Sebenta - Universidade do Minho

Transcrição

Sebenta - Universidade do Minho
Universidade do Minho Análise de dados com R
Miguel Rocha
Departamento de Informática Escola de Engenharia Braga, Março de 2013 Índice ÍNDICE 2 PREÂMBULO 5 1. INTRODUÇÃO AO SISTEMA R 6 1.1 O QUE É O R ? 6 1.2 INSTALAR O R 6 1.3 EDIÇÃO DE COMANDOS 7 1.4 OBJETOS E FUNÇÕES 7 1.5 AJUDA E DOCUMENTAÇÃO 8 1.6 GRAVAR/ RECUPERAR UMA SESSÃO 9 2. VETORES E TIPOS PRIMITIVOS 10 2.1 OBJETOS E ATRIBUIÇÃO DE VALORES 10 2.2 VALORES NUMÉRICOS E OPERADORES ARITMÉTICOS 10 2.3 VETORES NUMÉRICOS 11 2.4 ACESSO AOS ELEMENTOS DE VETORES 15 2.5 FUNÇÕES SOBRE VETORES NUMÉRICOS 16 2.6 VETORES LÓGICOS 20 2.7 STRINGS 22 2.8 FATORES 24 2.9 VALORES EM FALTA 26 EXERCÍCIOS RESOLVIDOS 26 3. ESTRUTURAS DE DADOS E FUNÇÕES 30 3.1 MATRIZES 30 3.2 ARRAYS 34 3.3 LISTS 35 3.4 DATA FRAMES 36 3.5 CONVERSÕES ENTRE ESTRUTURAS DE DADOS 40 EXERCÍCIOS RESOLVIDOS 41 4. LEITURA E ESCRITA DE DADOS 44 4.1 DEFINIÇÃO DA DIRETORIA DE TRABALHO 44 2 4.2 LEITURA DE DADOS 45 4.3 ESCRITA DE DADOS NO ECRÃ E EM FICHEIRO 46 5. PROGRAMAÇÃO DE NOVAS FUNÇÕES 49 5.1 DEFINIÇÃO DE NOVAS FUNÇÕES 49 5.2. INSTRUÇÕES CONDICIONAIS 50 5.3 INSTRUÇÕES CÍCLICAS 51 EXERCÍCIOS RESOLVIDOS 54 6. SUMARIZAÇÃO E PRÉ-­‐PROCESSAMENTO DOS DADOS 59 6.1 INTRODUÇÃO 59 6.2 VERIFICAÇÃO DA ESTRUTURA DOS DADOS 59 6.3 ESTATÍSTICA DESCRITIVA 61 6.4 TRATAMENTO DE VALORES OMISSOS 63 6.5 DISCRETIZAÇÃO DE VARIÁVEIS NUMÉRICAS 65 6.6 NORMALIZAÇÃO 66 EXERCÍCIOS RESOLVIDOS 67 7. VISUALIZAÇÃO DE DADOS: GRÁFICOS EM R 71 7.1 GRÁFICOS DE DISPERSÃO 71 7.2 BOXPLOTS 76 7.3 HISTOGRAMAS 80 7.4 GRÁFICOS DE BARRAS 82 7.5 GRÁFICOS DO TIPO PIE-­‐CHART 83 7.6 SOBREPOSIÇÃO DE OBJETOS GRÁFICOS 84 7.7 FORMATAÇÃO E EXPORTAÇÃO DE GRÁFICOS 86 EXERCÍCIOS RESOLVIDOS 88 8. REDUÇÃO DE DIMENSIONALIDADE 90 8.1 INTRODUÇÃO 90 8.2 ANÁLISE DE COMPONENTES PRINCIPAIS 90 8.3 DECOMPOSIÇÃO EM VALORES SINGULARES 93 EXERCÍCIOS RESOLVIDOS 96 9. CLUSTERING 99 9.1 INTRODUÇÃO 99 3 99 9.2 CLUSTERING HIERÁRQUICO 9.3 CLUSTERING K-­‐MEANS 102 EXERCÍCIOS RESOLVIDOS 103 10. INFERÊNCIA/ MODELAÇÃO ESTATÍSTICA 105 10.1 DISTRIBUIÇÕES DE PROBABILIDADE EM R 105 10.2 VERIFICAR GRAFICAMENTE A NORMALIDADE DOS DADOS 109 10.3 AMOSTRAGENS 111 10.4 INTERVALOS DE CONFIANÇA 112 10.5 TESTES ESTATÍSTICOS 113 10.6 ANÁLISE DE VARIÂNCIA 120 EXERCÍCIOS RESOLVIDOS 122 A. CASOS PRÁTICOS 124 A1. AS LARANJAS 124 ENUNCIADO 124 RESOLUÇÃO 125 A2. PROCESSO BIOLÓGICO 126 ENUNCIADO 126 RESOLUÇÃO 127 4 Preâmbulo Este texto foi escrito com o intuito de suavizar a abordagem aos conceitos de análise de dados utilizando o sistema R por alunos universitários com diversos tipos de formação. Assume-­‐se neste texto que os alunos não têm experiência prévia em programação nem na utilização de sistemas de computação científica/ estatística. No entanto, este texto não pretende explicar os métodos de análise de dados nem os seus fundamentos teóricos que devem ser estudados em outros textos especializados. O texto tem uma vertente marcadamente prática, sendo oferecidos inúmeros exemplos ao longo do texto, bem como exercícios resolvidos e propostos. Sugere-­‐se, assim, ao leitor que faça a leitura deste texto em paralelo com a utilização do sistema R e que experimente todos os exemplos propostos bem como todas as variantes que lhe sejam sugeridas pela sua imaginação. Só assim poderá tirar verdadeiro partido deste texto. Divirtam-­‐se com a análise de dados e com o R. 5 1. Introdução ao sistema R 1.1 O que é o R ? O R é um sistema de computação científica e estatística, programável e que permite o tratamento de vários tipos de dados. Na sua versão base possui um conjunto de ferramentas que permitem o armazenamento, processamento, cálculo, análise e visualização de dados. Possui ainda uma poderosa linguagem de programação que permite a implementação de novas funções com o comportamento definido pelo utilizador. Para além disso, é de acesso livre, existindo uma comunidade bastante ativa de investigadores (designada por CRAN) que desenvolvem funcionalidades que podem ser instaladas para estender as funcionalidades do sistema. O R começou por ser essencialmente desenvolvido como um sistema de tratamento estatístico de dados, mas tem evoluído no sentido de se tornar num ambiente de desenvolvimento coerente, mais genérico e multifacetado. Ainda assim, existem disponíveis na versão base do R muitas ferramentas de tratamento estatístico de dados e muitas outras estão disponíveis para instalação adicional e opcional. 1.2 Instalar o R O projeto R tem um sítio na WWW em http://www.r-­‐project.org, onde poderá encontrar a última versão disponível do software. Existem versões para Microsoft Windows, Linux e Mac OS X que podem ser instaladas livremente. Neste site estão também disponíveis uma série de sites alternativos (mirrors) onde podem encontrar os ficheiros de instalação, podendo ser escolhido um próximo da sua localização geográfica. A versão atual do R (em Fevereiro de 2013) é a 2.15. Para a instalação do R deverá escolher o mirror e a opção “base”, fazendo em seguida download do ficheiro de instalação adequado para o seu computador e sistema operativo. No MS Windows bastará em seguida correr o ficheiro de instalação para instalar o programa. Este criará um atalho no ambiente de trabalho, podendo também ser corrido da lista de programas. 6 1.3 Edição de comandos O R é um ambiente orientado ao uso de uma linha de comandos, apesar de nas versões do sistema operativo MS Windows e Mac OS X ter um conjunto de menus disponíveis que permitem realizar algumas operações. Note que existem programas, denominados de IDEs (integrated development environments), que criam um ambiente integrado que inclui a linha de comandos R e um conjunto de ferramentas complementares. Um exemplo recomendado deste tipo de software é o Rstudio (http://www.rstudio.com). Assim, o R permite escrever comandos a seguir ao prompt (>) do interpretador e executa-­‐os de seguida. Em muitos casos, o R não imprime no ecrã nenhum resultado da computação efectuada (e.g. por ser guardado em algum objeto) e apenas altera o estado das suas variáveis internas (ou objetos). Quando o resultado não é guardado num objeto ele é apenas mostrado no ecrã não podendo ser reutilizado. Para este efeito, o resultado deve ser armazenado em variáveis/ objetos do R. Para tornar o seu trabalho mais eficiente, pode sempre recuperar os comandos anteriores utilizando as setas do cursor, bem como editar os comandos com as teclas Delete ou Backspace, Home e End, que tomam os seus comportamentos habituais. Há que tomar em atenção que o R é sensível à diferença entre maiúsculas e minúsculas. Assim, quer o nome dos objetos quer os comandos deverão ser escritos na forma correta (e.g. “a” e “A” são símbolos diferentes). Quando um comando não é terminado numa dada linha o símbolo + aparece no início da linha indicando que o interpretador do R espera que o comando seja terminado. Quando se pretende criar um conjunto composto de comandos estes deverão ser iniciados com o símbolo { e terminados com o símbolo }. Os comandos individuais podem ser separados por ; ou mudando de linha. A função q( ) é usada para terminar a sua sessão no R. 1.4 Objetos e funções Existem dois conceitos fundamentais no R: objetos e funções. Os objetos (ou variáveis) são os “contentores” da informação, ou seja, permitem guardar dados de diversos tipos. Nos capítulos seguintes analisar-­‐se-­‐ão em detalhe os diversos tipos de objetos existentes no R. 7 As funções, por outro lado, constituem os mecanismos de processamento e manipulação dos dados. Tipicamente, uma função consta de uma operação de manipulação de dados que pode receber zero ou mais argumentos de entradas (objetos ou variáveis) e retorna como resultado um novo objeto ou variável (podendo não retornar resultado algum). As funções em R constituem assim uma implementação (ainda que mais flexível) do conceito matemático de função. As funções podem ser chamadas em R através da sintaxe: nome_função(argumentos) Quando o R arranca são carregados alguns packages que possuem um conjunto de funções já disponíveis. É possível carregar novos packages instalados na sua distribuição com a função library(package) tornando as funções desse package disponíveis. A função library, chamada sem argumentos – library() – permite saber quais os packages instalados. É ainda possível instalar novos packages disponíveis alargando assim o leque de funções disponíveis. Note que ao instalar um novo package terá que o carregar em seguida. No MS Windows e Mac OS X existe o menu Packages onde poderá encontrar opções que lhe permitem instalar novos packages e atualizar os existentes. Pode ainda instalar novos packages com a função install.package(packages). Por outro lado, o R disponibiliza ainda uma linguagem de programação que permite o desenvolvimento de novas funções pelo utilizador, que passam a estar disponíveis para serem utilizadas exatamente da mesma forma que as pré-­‐definidas (ver capítulo 5). 1.5 Ajuda e documentação O R possui um sistema de ajuda. Este pode ser consultado de várias formas: •
Consultar uma ajuda genérica sobre o R. O comando help.start() irá abrir uma janela de um browser (e.g. Internet Explorer ou Firefox) e mostrar uma página útil para uma introdução ao R. •
Usando a função help para descobrir ajuda sobre uma função do sistema (e.g., help(matrix)) •
Procurar todas as páginas de help onde esteja uma dada palavra chave com a função help.search (e.g. help.search("matrix")) 8 No MS Windows ou Mac OS X, ao nível do menu Help poderá ainda encontrar outras alternativas ao nível da ajuda ao utilizador. Neste menu tem ainda a opção de consultar alguns manuais disponíveis em PDF que acompanham a instalação do R. Uma função bastante útil é a função example(função) que permite consultar exemplos ilustrativos do uso de cada função, passando o nome da função como parâmetro. 1.6 Gravar/ recuperar uma sessão As seguintes funções poderão ser usadas para guardar o seu trabalho no final de uma sessão e recuperá-­‐lo na sessão seguinte: •
A função savehistory(nome_ficheiro) guarda os comandos efectuados durante uma sessão podendo especificar-­‐se o nome do ficheiro •
A função loadhistory(nome_ficheiro) carrega os comandos efectuados de um ficheiro. Este é um ficheiro de texto que poderá ser aberto por qualquer editor de texto. Estas opções estão também disponíveis no menu File. •
A função save.image(nome_ficheiro) guarda os dados criados durante uma sessão podendo especificar-­‐se o nome do ficheiro. •
A função load(nome_ficheiro) realiza a operação inversa. Este é um ficheiro binário que só pode ser aberto com o R. Ao terminar uma sessão tem a opção de guardar os dados e os comandos efetuados. Se responder afirmativamente, os dados serão guardados num ficheiro chamado “.RData” e os comandos no ficheiro “.Rhistory”, na sua pasta corrente de trabalho. Se o R for recomeçado da mesma diretoria estes ficheiros são automaticamente carregados no início da sessão seguinte. Na secção 4.1 poderá verificar mais detalhes sobre como verificar e alterar a sua diretoria de trabalho. 9 2. Vetores e tipos primitivos 2.1 Objetos e atribuição de valores Como foi já referido no capítulo anterior, o R permite definir objetos, que não são mais do que a forma de guardar informação de forma a poder ser re-­‐utilizada. A cada objeto no R é atribuído um nome que o identifica e um determinado tipo, que indica qual a estrutura dos dados que pode guardar. A operação mais básica que se pode realizar sobre um objeto é a atribuição, i.e, associar um valor ou conteúdo a um nome. Para o efeito usam-­‐se os operadores <-­‐ ou =. Assim os comandos seguintes colocam, ambos, o valor 2 no objeto x: > x = 2
> x <- 2
Note que quando efetua uma atribuição não é imprimido nenhum valor na linha de comando. Para verificar o conteúdo de um objeto terá que escrever o seu nome na linha de comando do R, sendo que este é imprimido no ecrã: > x
[1] 2
A lista de objetos disponíveis numa sessão de trabalho de R pode ser consultada através da função ls( ). A função rm(objeto) poderá ser usado para remover um objeto da sessão de trabalho. Por outro lado, a função typeof(objeto) permite determinar o tipo de dados associado ao objeto. 2.2 Valores numéricos e operadores aritméticos Pelas suas características, o tipo de dados mais usual de dados no R é o tipo numérico. Sobre valores numéricos é possível realizar diversos tipos de cálculos usando operadores aritméticos comuns ou funções pré-­‐definidas (que serão abordadas mais tarde). Para ilustrar algumas destas funcionalidades, vejamos exemplos onde a linha de comandos do R é usada ao estilo uma calculadora: 10 > 2+3
[1] 5
> 3 * 1/2
[1] 1.5
> 5.3 - 4.2 / 2.3
[1] 3.473913
Note nos exemplos acima que quando escreve uma expressão na linha de comandos R, o seu valor é calculado e escrito no ecrã. O valor 1 entre parêntesis retos indica apenas que se trata do primeiro elemento do resultado, e neste caso o único. Note ainda que as regras de prioridade dos operadores aritméticos se mantêm. Vimos já acima como criar um objeto numérico e atribuir-­‐lhe um valor. Vejamos em seguida mais alguns exemplos ilustrativos: > y = 3.1
> z = 6.2
> y / z
[1] 0.5
> w = y * z + 2
Neste caso, os operadores aritméticos +, -­‐ e / têm o significado habitual, enquanto * significa multiplicação. Podem ainda usar-­‐se a exponenciação (símbolo ^), a divisão inteira (símbolo %/%) e o resto da divisão inteira (%%), como se pode observar pelos exemplos: > y=5
> y^2
[1] 25
> y%%2
[1] 1
> y%/%2
[1] 2
2.3 Vetores numéricos Vimos anteriormente exemplos de variáveis escalares. No entanto, o R é orientado aos vetores que constituem objetos que permitem guardar uma sequência de 11 valores. Assim, não existem em R variáveis escalares com um valor, sendo estas casos particulares de vetores com apenas um elemento. A função c permite concatenar um vetor com outros elementos (ou com outros vetores), sendo a forma mais direta de formar um vector: > v1 = c(1,5,8,10)
> v1
[1]
1
5
8 10
> v2 = c(3,v1)
> v2
[1]
3
1
5
8 10
Outra forma de criar vetores numéricos, com utilidade prática, é a geração de sequências de números. Um dos casos mais simples consiste em gerar, por exemplo, um vetor com todos os números inteiros entre 1 e 10. Podemos fazer isso utilizando o operador :, como se ilustra de seguida: > 1:10
[1]
1
2
3
4
5
6
7
8
9 10
> 10:2
[1] 10
9
8
7
6
5
4
3
2
É ainda possível gerar sequências duma forma mais genérica utilizando a função seq. Esta tem como argumentos: o valor inicial da sequência (from), o valor final (to) e intervalo entre os elementos (by). Vejamos um exemplo simples de uma sequência entre 1.0 e 5.0, com passo de 0.1. > seq(1, 2, 0.1)
[1]
1.0
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
2.0
Em R podemos escrever os argumentos das funções pela sua ordem ou pelo nome podendo assim defini-­‐los por uma outra ordem. Assim, este comando dá exatamente o mesmo resultado que o anterior: > seq(by = 0.1, to = 5, from = 1)
12 Por outro lado, podemos ainda omitir alguns argumentos, desde que estes tenham um valor por omissão. No exemplo seguinte, o parâmetro by foi omitido tendo o valor 1 por omissão: > seq(1, 10)
[1]
1
2
3
4
5
6
7
8
9 10
Uma função relacionada com esta é a função rep, que permite criar vectores com repetição de elementos. Os principais argumentos desta função definem os elementos a repetir, o número de vezes que determinado elemento é repetido (each) e o número total de repetições a efetuar (times). Os exemplos seguintes ilustram o comportamento da função: > x = c(2,4,6)
> rep(x, times=5)
[1] 2 4 6 2 4 6 2 4 6 2 4 6 2 4 6
> rep(x, each=5)
[1] 2 2 2 2 2 4 4 4 4 4 6 6 6 6 6
De uma forma mais genérica, um vetor pode ser criado usando a função vector. Esta função recebe como parâmetros o tipo de dados do vector denominado de mode (que pode ser “numeric” para vetores numéricos, “logical” no caso de vetores lógicos ,ou “character” no caso das strings que se tratarão em secções próximas) e o tamanho (length): > v = vector(mode="numeric", length=10)
> v
[1] 0 0 0 0 0 0 0 0 0 0
Uma forma alternativa e útil de gerar vetores numéricos passa pela sua geração aleatória a partir de uma dada distribuição estatística, das quais se podem destacar: •
runif: gera valores aleatórios num dado intervalo onde cada valor tem a mesma probabilidade de ser gerado. Recebe como argumentos o número de elementos a gerar, o limite inferior e o limite superior do intervalo (por omissão estes valores são 0 e 1). 13 •
rnorm: gera valores de uma distribuição normal. Recebe como argumentos o número de elementos a gerar, a média e o desvio padrão da distribuição. •
rpois: gera valores de uma distribuição de Poisson (discretos), recebendo como argumentos o número de valores e o parâmetro λ da distribuição. Alguns exemplos: > x=runif(10)
> x
[1]
0.20005861
0.52085024
0.71153606
0.79749557
0.05269482
0.43319890
[7] 0.75369573 0.06322223 0.22304411 0.76038862
> y = runif(10,5,10)
> y
[1] 9.918916 7.585866 8.772228 8.953732 6.417860 5.893641 8.355221
7.710591
[9] 9.907576 9.939798
É possível realizar operações aritméticas sobre vetores de uma forma bastante simples e análoga à forma como realizamos as operações sobre valores individuais (escalares), como se pode verificar pelos exemplos seguintes: > x = 1:10
> 2*x+1
[1]
3
5
7
9 11 13 15 17 19 21
> x/2
[1] 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0
> x-5
[1] -4 -3 -2 -1
0
1
2
3
4
5
Uma operação aritmética utiliza vetores do mesmo tamanho e realiza a operação entre cada par de valores nas mesmas posições de ambos os vetores. Se os vetores não tiverem o mesmo tamanho, o mais pequeno será reciclado, isto é, será repetido enquanto for necessário. Isto acontece normalmente em R quando é necessário um vetor de um certo tamanho e se usa um mais pequeno. Vejamos um exemplo, onde cada elemento do vetor vm é usado duas vezes enquanto cada elemento de vn é apenas usado uma: 14 > vm = 1:5
> vn = 1:10
> vm+vn
[1]
2
4
6
8 10
7
9 11 13 15
Quando o tamanho do vetor maior não é múltiplo do menor tem-­‐se uma mensagem de aviso, pois este caso é muitas vezes sinónimo de comportamentos não desejados pelo utilizador. Veja-­‐se o exemplo seguinte: > vm = 1:3
> vn = 1:10
> vm+vn
[1]
2
4
6
5
7
9
8 10 12 11
Warning message:
longer object length is not a multiple of shorter object length in:
vm + vn
2.4 Acesso aos elementos de vetores É possível consultar certos índices de um vetor escrevendo o nome do vetor e, entre parêntesis rectos, os índices. Assim, se quisermos aceder ao elemento do vetor x na posição i escrevemos x[i]. Um exemplo simples: > x
[1]
1
2
3
4
5
6
7
8
9 10
> x[2]
[1] 2
A indexação pode também ser feita usando um vetor como índice, sendo neste caso retornado um vetor com os valores do vetor inicial nas posições constantes do vetor índice. Veja-­‐se um exemplo: > v1
[1]
1
5
8 10
> ind=2:3
> v1[ind]
[1] 5 8
15 Se os índices forem negativos, isto quer dizer que todos os valores excepto os valores dos índices são selecionados: > v1[-2]
[1]
1
8 10
> v1[-(2:3)]
[1]
1 10
O R permite ainda atribuir nomes a cada uma das posições do vetor e depois usar esses nomes para aceder aos seus valores. Os nomes associados a um vetor são atribuídos usando a função names: > v1 = c(1,5,8,10)
> names(v1) = c("azul","amarelo","verde","vermelho")
> v1
azul
amarelo
1
5
verde vermelho
8
10
> v1["verde"]
verde
8
> v1[c("amarelo","vermelho")]
amarelo vermelho
5
10
Os nomes associados a um vetor são, de facto, um vetor de strings que serão abordados em mais detalhe numa das próximas secções. 2.5 Funções sobre vetores numéricos Existem inúmeras funções que podem ser aplicadas sobre vetores numéricos. Um grupo de funções pode ser identificado como funções matemáticas tipicamente aplicadas a um único valor e que no R são aplicadas individualmente a todos os elementos de um vetor, gerando um novo vetor com o mesmo comprimento. Estas podem obviamente ser aplicadas a vetores com um único elemento, usadas para representar variáveis escalares. Identificam-­‐se aqui algumas das mais usadas: •
Valor absoluto (módulo): abs; 16 •
Raiz quadrada: sqrt; •
Funções trigonométricas: seno (sin), coseno (cos), tangente (tan); •
Arredondar um valor para um determinado número de casas decimais: round; •
Exponenciação natural (ex): exp; •
Logaritmos: log (logaritmo natural), logb (logaritmo em que a base é passada por argumento), log2 (logaritmo base 2) e log10 (logaritmo base 10). Alguns exemplos (de referir que no exemplo é usada a constante pi, que representa o valor de П): > v =c(-1,2,3,-4)
> abs(v)
[1] 1 2 3 4
> sqrt(abs(v))
[1] 1.000000 1.414214 1.732051 2.000000
> sin(pi/2*v)
[1] -1.000000e+00
1.224606e-16 -1.000000e+00
2.449213e-16
> nr = c(1.23, 2.321, 4.07654, 3, 2.345)
> round(nr, 1)
[1] 1.2 2.3 4.1 3.0 2.3
> p10 = c(10,100,1000,10000)
> log10(p10)
[1] 1 2 3 4
A função mapply permite ainda aplicar qualquer função (ou operador aritmético) do R ou definida pelo utilizador a todos os elementos de um vetor, recebendo como argumentos o nome da função e o vetor: > mapply(sqrt, abs(v))
[1] 1.000000 1.414214 1.732051 2.000000
> mapply("/",p10,10)
[1]
1
10
100 1000
Por outro lado, algumas funções são aplicadas a um vector gerando um novo vector. Entre estas poderão destacar-­‐se: •
sort: ordena um vetor por ordem crescente ou decrescente; 17 •
order: dá a ordem dos elementos do vetor (pelos seus índices); •
unique: dá um vetor apenas com os elementos únicos (sem repetidos); •
rev: dá o inverso de um vector; •
cumsum: dá a soma cumulativa de um vetor, i.e. em cada posição o resultado é a soma dos elementos anteriores; •
cumprod: dá o produto cumulativo de um vetor; •
diff: retorna vetor com as primeiras diferenças do original. Alguns exemplos de utilização: > v = c(2,4,7,6,3,1)
> rev(v)
[1] 1 3 6 7 4 2
> sort(v)
[1] 1 2 3 4 6 7
> sort(v, decreasing=TRUE)
[1] 7 6 4 3 2 1
> order(v)
[1] 6 1 5 2 4 3
> v[order(v)]
[1] 1 2 3 4 6 7
> unique(c(1,2,1,2,3))
[1] 1 2 3
> cumsum(v)
[1]
2
6 13 19 22 23
> cumprod(v)
[1]
2
8
56
336 1008 1008
> diff(v)
[1]
2
3 -1 -3 -2
Um outro conjunto de funções permite calcular valores escalares a partir de um vetor. Neste caso, têm-­‐se os seguintes exemplos: •
Número de elementos (comprimento) de um vector: length •
Menor valor do vetor (min), índice do menor valor (which.min), maior valor (max), índice do maior valor (which.max); •
Soma dos elementos de um vector: sum •
Média dos elementos de um vector: mean 18 Note-­‐se que a função length pode aplicar-­‐se a qualquer tipo de vetor. Alguns exemplos: > vs = c(3,5,7,9)
> sum(vs)
[1] 24
> mean(vs)
[1] 6
> min(v)
[1] 3
> max(v)
[1] 9
> which.max(v)
[1] 4
> which.min(v)
[1] 1
> range(v)
[1] 3
9
O tipo de dados existente num dado vector pode ser consultado com a função typeof: > typeof(v1)
[1] "double"
Neste caso, o tipo “double” corresponde a valores numéricos usando uma representação interna de precisão dupla que permite guardar um leque alargado de valores e é o modo por omissão para valores numéricos. Uma outra função usada para determinar o tipo de objetos com que se está a trabalhar é a função class. Um vector numérico tem como classe “numeric”. Por outro lado, os vetores lógicos que se tratarão na próxima secção têm por classe e por tipo o valor “logical”. O R permite trabalhar com números complexos especificando-­‐se a componente imaginária colocando um valor numérico e o símbolo i. Um exemplo de um vetor de valores complexos é o seguinte: > vc = c(2+3i, 4-21, 3i, 4)
> sum(vc)
[1] -11+6i
> mean(vc)
19 [1] -2.75+1.5i
No exemplo anterior, é possível verificar que se aplicam a estes valores as mesmas funções que a valores reais (pelo menos quando tal fizer sentido do ponto de vista matemático). Um vetor passa a ter o tipo de dados “complex” se pelo menos um dos seus membros for um número complexo. Assim, se quisermos calcular raízes quadradas de valores negativos devemos defini-­‐los como complexos, conforme o exemplo seguinte: > vc = c(4, -4)
> sqrt(vc)
[1]
2 NaN
Warning message:
NaNs produced in: sqrt(vc)
> vc = c(4+1i, -4)
> sqrt(vc)
[1] 2.015329+0.248098i
2.6 Vetores lógicos Um outro tipo de dados importante no R são os vetores lógicos, cujos elementos são variáveis Booleanas ou lógicas, ou seja, que podem tomar o valor verdadeiro (representado como TRUE) ou falso (FALSE). Estes vectores podem ser criados diretamente a partir da função c referido anteriormente: > vb1 = c(TRUE,TRUE,FALSE,TRUE,FALSE)
> vb1
[1]
TRUE
TRUE FALSE
TRUE FALSE
> vb1[3]
[1] FALSE
Em alternativa, este tipo de vetores pode ser criado a partir de um vetor numérico, aplicando a cada elemento uma condição que define se o valor a colocar nessa posição é verdadeiro ou falso. Um exemplo simples segue: > v = 1:10
> vb2 = v > 5
20 > vb2
[1] FALSE FALSE FALSE FALSE FALSE
TRUE
TRUE
TRUE
TRUE
TRUE
Ao nível das condições, poder-­‐se-­‐ão utilizar os operadores >, <, >=, <= com os significados habituais. A igualdade é representada por == e a desigualdade por !=. Note a diferença entre o símbolo = para a atribuição, e o símbolo == para definir uma condição. Por exemplo x = 3, significa que o objeto x passa a ter como conteúdo o valor 3, enquanto que x == 3 é uma expressão lógica que teste se o conteúdo do objeto x é 3, podendo ter um valor de TRUE ou FALSE. Um outro operador que retorna um valor lógico é o operador %in% que tem o significado da pertença de conjuntos. Por outro lado, podem aplicar-­‐se a vetores lógicos os operadores lógicos habituais: •
negação: representada pelo símbolo ! (e.g. !c representa a negação de c); •
conjunção: representada pelo símbolos &. •
disjunção: representada pelos símbolos |. Estes operadores podem ser usados para definir condições mais complexas. A forma de usar estes operadores segue a filosofia vetorial apontada para o caso dos operadores aritméticos, como se ilustra nos exemplos. Nestes demonstra-­‐se ainda o uso das funções all e any. Como os nomes indicam, a primeira retorna TRUE apenas quando todos os elementos do vetor forem TRUE, enquanto a segunda retorna TRUE se pelo menos um dos elementos for TRUE. Finalmente, a função which pode ser usada sobre um vetor lógico para identificar os índices dos elementos TRUE. > vb2 | vb1 [1] TRUE TRUE FALSE TRUE FALSE TRUE TRUE TRUE TRUE TRUE > vb1 & !vb2 [1] TRUE TRUE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE > all(vb1)
[1] FALSE
> any(vb1)
[1] TRUE
> which (vb1)
[1] 1 2 4
Os vetores lógicos podem ainda ser usados como vetores numéricos, sendo que o valor de TRUE é tomado como 1 e o valor de FALSE é tomado como 0. 21 Uma outra funcionalidade interessante dos vetores lógicos é a sua utilização como forma de indexar outros vetores. Neste caso, os valores dos índices para o qual o vetor lógico é verdadeiro são considerados e os restantes são ignorados. Seguem-­‐se exemplos demonstrativos: > ve = 8:2
> ve
[1] 8 7 6 5 4 3 2
> vb = ve > 5
> vb
[1]
TRUE
TRUE
TRUE FALSE FALSE FALSE FALSE
> ve[vb]
[1] 8 7 6
> ve[ve<6]
[1] 5 4 3 2
2.7 Strings Um dos tipos de vetores existentes em R permite guardar em cada posição sequências de caracteres, também designadas por strings. Um exemplo muito simples da utilização deste tipo de variáveis é dado em seguida: > cores = c("azul","amarelo","verde","vermelho")
> cores[3]
[1] "verde"
> muitascores = rep(cores, 2)
> muitascores
[1] "azul"
"amarelo"
"verde"
"vermelho" "azul"
"amarelo"
"verde"
[8] "vermelho"
> muitascores[1] == muitascores[5]
[1] TRUE
Existem um conjunto de funções específicas para trabalhar sobre strings, das quais se podem destacar as seguintes: •
nchar: dá o número de caracteres de uma string; •
paste: concatena os valores de dois vectores de strings; 22 •
substr: permite extrair uma sub-­‐string de uma string, permitindo por exemplo retirar prefixos e sufixos; •
toupper/ tolower: convertem uma string para maiúsculas ou minúsculas, respectivamente; •
grep: permite procurar padrões em strings; •
sub/ gsub: permitem procurar e substituir padrões em strings (o sub subsitui apenas a primeira ocorrência do padrão enquanto o gsub substitui todas as ocorrências); •
chartr: permite substituir em paralelo um conjunto de caracteres por um outro, definidos posição a posição. O comportamento destas funções é exemplificado pelos seguintes exemplos: > nome = "paulo"
> apelido = "silva"
> toupper(nome)
[1] "PAULO"
> paste(nome, apelido)
[1] "paulo silva"
> substr(apelido,2,4)
[1] "ilv"
> nchar(nome)
[1] 5
> sub("lo","la",nome)
[1] "paula"
> str = "abracadabra"
> gsub("a","x",str)
[1] "xbrxcxdxbrx"
> chartr("abc","ABC",str)
[1] "ABrACAdABrA"
Algumas destas funções podem ser aplicadas a vetores de strings e não apenas a uma string individualmente. No caso de os vectores não serem do mesmo tamanho aplica-­‐se a regra anterior de recomeçar do início do vetor mais pequeno. A função mapply definida anteriormente pode também ser usada com vetores de strings. Seguem-­‐se alguns exemplos: > nomes = c("joao", "joaquim", "jose")
> apelidos = c("silva", "sousa")
> paste(nomes, apelidos, sep=" ")
23 [1] "joao silva"
"joaquim sousa" "jose silva"
> toupper(nomes)
[1] "JOAO"
"JOAQUIM" "JOSE"
> mapply(nchar,nomes)
joao joaquim
4
jose
7
4
> sub("j","J",nomes)
[1] "Joao"
"Joaquim" "Jose"
Note-­‐se ainda a existência em R de duas constantes pré-­‐definidas que guardam o conjunto de letras maiúsculas e minúsculas: > letters
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p"
"q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
> LETTERS
[1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P"
"Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
2.8 Fatores Os fatores constituem um tipo de dados existente no R que permite representar variáveis cuja gama de valores possíveis é um conjunto discreto. Um objeto do tipo fator pode ser construído a partir de um vetor de strings, sendo identificados os valores distintos existentes que tomam o nome de “levels”. A função factor efetua esta tarefa, sendo a função levels aplicada sobre um objeto do tipo factor para determinar o conjunto de valores distintos. Alguns exemplos: > racas = c("bulldog", "rafeiro", "doberman", "rafeiro", "bulldog",
"rafeiro", "rafeiro", "doberman")
> fr = factor(racas)
> fr
[1] bulldog
rafeiro
doberman rafeiro
bulldog
rafeiro
rafeiro
doberman
Levels: bulldog doberman rafeiro
> levels(fr)
[1] "bulldog"
"doberman" "rafeiro"
24 A principal utilização dos factores surge na análise estatística, podendo dividir-­‐
se os dados pelas classes ou categorias presentes. Pode usar-­‐se a função table para criar-­‐se uma tabela de frequências de cada uma das categorias: > table(fr)
fr
bulldog doberman
2
rafeiro
2
4
Podem também calcular-­‐se diversas estatísticas sobre cada uma das categorias. Para o efeito uma função importante é a função tapply. Esta recebe como argumentos um vetor com a variável a analisar estatisticamente, um vector com os factores e uma função a aplicar aos dados. Por exemplo, suponha-­‐se que no exemplo anterior se tem um vector com o peso de cada um dos cães considerados e que se pretende calcular a média de pesos por raça: > pesos = c(12, 15, 35, 10, 20, 8, 13, 25)
> tapply(pesos,fr,mean)
bulldog doberman
16.0
30.0
rafeiro
11.5
A função ordered pode ser usada para criar um objeto do tipo factor, em que estes são ordenados por uma ordem definida pelo utilizador. >
tamanhos
=
c("medio",
"medio",
"grande",
"pequeno",
"grande",
"pequeno", "medio", "grande")
> of = ordered(tamanhos, c("pequeno","medio","grande"))
> of
[1] medio
medio
grande
pequeno grande
pequeno medio
grande
Levels: pequeno < medio < grande
> tapply(pesos, of, range)
$pequeno
[1]
8 10
$medio
[1] 12 15
$grande
[1] 20 35
25 2.9 Valores em falta O R permite que num vetor possam existir valores que não são, em dado momento, conhecidos tendo um valor especial – NA – que representa estes casos1. Este valor poderá ser usado em casos onde o valor de uma dada variável não é conhecido ou ainda não foi inicializado. Note que este é um valor especial, pelo que o teste x == NA terá sempre como resultado FALSE, mesmo que x não seja conhecido e tenha valor NA. A função is.na pode ser aplicada a um vector, retornando um vector lógico que tem o valor TRUE nas posições onde o vector original tem o valor NA. Esta função pode ser usada, por exemplo, para filtrar de um vetor todos os elementos NA antes de realizar alguma operação: > v = c(NA, 2, 3, NA, 5)
> v
[1] NA
2
3 NA
5
> v[is.na(v)]
[1] NA NA
> v[!is.na(v)]
[1] 2 3 5
Um segundo tipo de valor especial é o NaN, que deriva de “Not A Number”, usado para representar situações de erro em alguns tipos de cálculo, por exemplo 0/0. É de referir que existe ainda uma constante – Inf – que representa o valor de infinito. Este será o resultado, por exemplo, de dividir qualquer valor por zero. A expressão Inf – Inf produzirá NaN como resultado. De forma semelhante à anterior existe também uma função is.nan. Exercícios resolvidos 1. i) Crie duas variáveis x e y com valores numéricos inteiros. ii) Calcule a sua soma, subtração, multiplicação, divisão, divisão inteira e resto da divisão inteira. 1
NA deriva de “Not available”
26 > x=20
> y=7
> x+y
[1] 27
> x/y
[1] 2.857143
> x*y
[1] 140
> x%%y
[1] 6
> x%/%y
[1] 2
2. Calcule o seno, o coseno e a tangente de π/6 . > a = pi/6
> cos(a)
[1] 0.8660254
> sin(a)
[1] 0.5
> tan(a)
[1] 0.5773503
3. i) Defina uma variável com a sequência dos números ímpares menores do que 40. ii) Calcule a soma deste vector. iii) Selecione os membros desta sequência que são múltiplos de 3. iv) Calcule a divisão da sequência obtida em iii) por 3. > si = seq(1,39,2)
> si
[1]
1
3
5
7
9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39
> sum(si)
[1] 400
> si[si%%3==0]
[1]
3
9 15 21 27 33 39
> si[si%%3==0] / 3
[1]
1
3
5
7
9 11 13
4. Calcule a soma dos números inteiros pares menores do que 1000. 27 > sum(seq(2,998,2))
[1] 249500
5. Defina um vetor com a raiz quadrada dos números inteiros de 1 até 20. > sqrt(1:20)
[1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751 2.828427
[9] 3.000000 3.162278 3.316625 3.464102 3.605551 3.741657 3.872983 4.000000
[17] 4.123106 4.242641 4.358899 4.472136
6. i) Defina um vetor p com os 20 primeiros números naturais. ii) Calcule o vetor inverso de p. iii) Calcule o vetor soma de p com o seu inverso. iv) Calcule o vetor resultante de somar a constante 3 a cada elemento de p. v) Calcule o vetor 1/p. > p = 1:20
> rev(p)
[1] 20 19 18 17 16 15 14 13 12 11 10
9
8
7
6
5
4
3
2
1
> p+rev(p)
[1] 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21
> p+3
[1]
4
5
6
7
8
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
> 1/p
[1] 1.000000 0.500000 0.333333 0.250000 0.200000 0.166667
[7] 0.142857 0.125000 0.111111 0.100000 0.090909 0.083333
[13] 0.076923 0.071428 0.066667 0.062500 0.058823 0.055556
[19] 0.052631 0.050000
7. i) Crie uma variável do tipo string fazendo cor = “azul”. Verifique o seu valor. ii) Crie uma outra variável cor2, atribuindo-­‐lhe o valor “amarelo”. iii) Teste a função paste para criar uma variável que seja a junção de ambas as variáveis anteriores. > cor="azul"
> cor2 = "amarelo"
> paste(cor, cor2)
[1] "azul amarelo"
28 8. Dada uma string com um nome, substitua o primeiro carácter pela letra maiúscula correspondente. > nome="paulo"
> paste(toupper(substr(nome,1,1)),substr(nome,2,nchar(nome)),sep="")
[1] "Paulo"
9. Dado um vetor com números de alunos, um vetor com os seus nomes e um vetor com as suas notas, criar um vetor de strings com elementos na forma: “número – nome – nota”. > numeros = c(23, 37, 51)
> nomes = c("Joao", "Pedro", "Joaquim")
> notas = c(10, 12, 14)
> paste (numeros, nomes, notas, sep=" - ")
[1] "23 - Joao - 10"
"37 - Pedro - 12"
"51 - Joaquim - 14"
29 3. Estruturas de dados e funções Neste capítulo abordar-­‐se-­‐ão diversas estruturas de dados primitivas do R, as suas capacidades e principais funções. Cobrir-­‐se-­‐ão os casos das matrizes, arrays, lists e data frames. 3.1 Matrizes O R permite definir matrizes de uma forma bastante similar ao conceito de vetor, mas agora com duas dimensões de dados: as linhas e as colunas. Uma matriz pode ser criada usando a função matrix, que toma como argumentos um vetor com os dados, o número de linhas e o número de colunas: > mat = matrix(1:20, 4, 5)
> mat
[,1] [,2] [,3] [,4] [,5]
[1,]
1
5
9
13
17
[2,]
2
6
10
14
18
[3,]
3
7
11
15
19
[4,]
4
8
12
16
20
Os comandos cbind e rbind permitem definir matrizes de outras formas. Veja os exemplos dados a seguir. Repare que cbind junta colunas (sejam estas em simples vectores ou mesmo matrizes) e rbind junta linhas (podendo, tal como cbind, estas serem vetores ou matrizes). > x=1:10
> m1 <- cbind(x, x^2, x^3)
> m1
x
[1,]
1
1
[2,]
2
4
8
[3,]
3
9
27
[4,]
4
16
64
[5,]
5
25
125
[6,]
6
36
216
[7,]
7
49
343
1
30 [8,]
8
64
512
[9,]
9
81
729
[10,] 10 100 1000
> rbind(m1, c(20, 40, 60))
x
[1,]
1
1
1
[2,]
2
4
8
[3,]
3
9
27
[4,]
4
16
64
[5,]
5
25
125
[6,]
6
36
216
[7,]
7
49
343
[8,]
8
64
512
[9,]
9
81
729
[10,] 10 100 1000
[11,] 20
40
60
A indexação de elementos na matriz faz-­‐se de uma forma semelhante ao dos vetores, indicando-­‐se agora entre parêntesis rectos o valor da linha e o da coluna separados por uma vírgula: > mat[2,3]
[1] 10
> mat[1:2,4:5]
[,1] [,2]
[1,]
13
17
[2,]
14
18
O valor da linha ou da coluna poderão ser omitidos, tendo como resultado um vetor que representa toda uma linha ou toda uma coluna: > mat[2,]
[1]
2
6 10 14 18
> mat[,1]
[1] 1 2 3 4
> mat[2:3,]
[,1] [,2] [,3] [,4] [,5]
[1,]
2
6
10
14
18
[2,]
3
7
11
15
19
A função is.matrix pode ser usada para determinar se uma variável é uma matriz. As dimensões de uma matriz podem obter-­‐se usando as funções nrow para o 31 número de linhas e ncol para o número de colunas. A função dim dá um vector com os dois valores anteriores: > dim(mat)
[1] 4 5
> nrow(mat)
[1] 4
> ncol(mat)
[1] 5
É possível alterar as dimensões de uma matriz usando a função dim para atribuir um novo vetor: > mat1 = matrix(8:1,4,2)
> mat1
[,1] [,2]
[1,]
8
4
[2,]
7
3
[3,]
6
2
[4,]
5
1
> dim(mat1) = c(2,4)
> mat1
[,1] [,2] [,3] [,4]
[1,]
8
6
4
2
[2,]
7
5
3
1
As operações aritméticas funcionam nas matrizes de forma semelhante ao que acontece com os vetores. Podem também aplicar-­‐se a matrizes muitas das funções definidas no âmbito dos vetores numéricos, sendo neste caso aplicadas a todos os elementos da matriz, gerando uma nova matriz com as mesmas dimensões. Em seguida, mostram-­‐se alguns exemplos: > mean(mat)
[1] 10.5
> sqrt(mat[2:3,3:4])
[,1]
[,2]
[1,] 3.162278 3.741657
[2,] 3.316625 3.872983
> cos(pi*mat)
[,1] [,2] [,3] [,4] [,5]
[1,]
-1
-1
-1
-1
-1
[2,]
1
1
1
1
1
32 [3,]
-1
-1
-1
-1
-1
[4,]
1
1
1
1
1
Existem algumas diferenças nas funções da família apply, ou seja, que permitem a aplicação de uma função a todos os elementos de um vetor/ matriz. De facto, para matrizes é definida a função apply que permite a aplicação de uma mesma função a todas as linhas ou a todas as colunas de uma matriz. Assim, esta função toma como argumentos a matriz, a dimensão (1 para as linhas, 2 para as colunas) e o nome da função. O exemplo seguinte define um vetor com a soma das linhas de uma matriz: > apply(mat, 1, sum)
[1] 45 50 55 60
Em relação aos operadores aritméticos existe um operador que apenas pode ser aplicado a duas matrizes: a multiplicação de matrizes, que se representa em R pelos símbolos %*% (note-­‐se que o símbolo * denota a multiplicação de uma matriz por um escalar ou por um vetor): > A = matrix(1:4,2,2)
> B = matrix(4:1,2,2)
> C= A%*%B
> C
[,1] [,2]
[1,]
13
5
[2,]
20
8
Existem ainda algumas funções que são próprias do cálculo sobre matrizes, das quais se podem salientar: •
t: calcula a matriz transposta de uma matriz; •
det: calcula o determinante de uma matriz; •
eigen: calcula valores e vetores próprios de uma matriz (o resultado é uma list, estrutura de dados que será abordada no final deste capítulo). •
solve: resolve sistemas de equações lineares, recebendo como argumento a matriz de coeficientes das variáveis e o vetor de termos independentes. Alguns exemplos: 33 > t(C)
[,1] [,2]
[1,]
13
20
[2,]
5
8
> det(C)
[1] 4
Como exemplo, se quisermos usar a função solve para resolver o seguinte sistema de equações: x1 + 3x2 = 1 2x1 + 4x2 = 3 > A = matrix(1:4,2,2)
> b = c(1,3)
> solve (A,b)
[1]
2.5 -0.5
O resultado é um vetor com os valores de x1 e x2. 3.2 Arrays Os arrays constituem uma generalização das matrizes ao caso mais genérico de N dimensões. De facto, uma matriz é no R um caso particular de um array com N=2. Os arrays podem criar-­‐se usando a função array, especificando os dados e o vector dim. O vetor dim terá N valores, indicando em cada posição a dimensão do array para essa coordenada. Por exemplo: > a = array(1:24, c(4,3,2))
> a
, , 1
[,1] [,2] [,3]
[1,]
1
5
9
[2,]
2
6
10
[3,]
3
7
11
[4,]
4
8
12
, , 2
[,1] [,2] [,3]
[1,]
13
17
21
[2,]
14
18
22
[3,]
15
19
23
[4,]
16
20
24
34 As formas de indexação e as funções definidas anteriormente para vetores e matrizes têm uma generalização bastante intuitiva para o nível dos arrays, como se pode verificar nos seguintes exemplos ilustrativos: > a[3,2,2]
[1] 19
> a[3,2,]
[1]
7 19
> a[,,1]
[,1] [,2] [,3]
[1,]
1
5
9
[2,]
2
6
10
[3,]
3
7
11
[4,]
4
8
12
> sum(a)
[1] 300
> mean(a[1,,])
[1] 11
> cos(pi*a[1,,])
[,1] [,2]
[1,]
-1
-1
[2,]
-1
-1
[3,]
-1
-1
3.3 Lists Os objetos do tipo list constituem no R estruturas de dados alternativas quando pretendemos agrupar variáveis de tipos diferentes num mesmo objeto. Um objeto do tipo list constitui assim um conjunto de variáveis, designadas por componentes, sendo cada um destes identificado por um nome. Estes objetos podem ser criados através da função list, indicando-­‐se os nomes dos componentes e os seus conteúdos: >
auto
=
list(marca="ford",
modelo="fiesta",
nportas=5,
velocMax=155, consumos=c(6,7.1,9.3))
> auto
$marca
[1] "ford"
$modelo
[1] "fiesta"
$nportas
35 [1] 5
$velocMax
[1] 155
$consumos
[1] 6.0 7.1 9.3
Uma lista pode ser indexada de várias formas. Assim, a indexação com [] devolve uma nova lista com o(s) campo(s) selecionados. Por outro lado, a indexação com $ seguido do nome do campo permite obter a variável correspondente a esse campo. Esta última forma é equivalente à indexação com [[ ]] usando um índice numérico. Seguem-­‐se alguns exemplos ilustrativos: > auto[2:3]
$modelo
[1] "fiesta"
$nportas
[1] 5
> is.list(auto)
[1] TRUE
> auto[[1]]
[1] "ford"
> auto$marca
[1] "ford"
> auto$consumos[1]
[1] 6
No exemplo anterior, a função is.list testa se um objecto é, ou não, uma lista. A função length aplicada sobre uma lista retorna o número de componentes da lista. 3.4 Data frames Os data frames constituem tipos especiais de lists onde os componentes são vetores numéricos ou fatores e todos eles têm o mesmo comprimento. Os vetores de strings dentro de um data frame são convertidos para vetores de fatores. Assim, o data frame permite a co-­‐existência de variáveis numéricas e de variáveis discretas (também designadas por categóricas ou nominais). Por esta razão, o data frame constitui a estrutura de dados mais usada em R para a tarefas de análise de dados. Os data frames podem ainda ser encarados como matrizes especiais onde pode haver um tipo diferente para cada coluna. 36 Um data frame pode ser construído usando a função data.frame, definindo-­‐se os diversos vetores que o constituem como se pode observar no exemplo ilustrativo que se segue: > racas = c("bulldog", "rafeiro", "doberman", "rafeiro", "bulldog",
"rafeiro", "rafeiro", "doberman")
> pesos = c(12, 15, 35, 10, 20, 8, 13, 25)
>
tamanhos
=
c("medio",
"medio",
"grande",
"pequeno",
"grande",
"pequeno", "medio", "grande")
> df = data.frame(racas, tamanhos, pesos)
> df
racas tamanhos pesos
1
bulldog
medio
12
2
rafeiro
medio
15
3 doberman
grande
35
4
rafeiro
pequeno
10
5
bulldog
grande
20
6
rafeiro
pequeno
8
7
rafeiro
medio
13
8 doberman
grande
25
O acesso a cada um seus componentes (ou campos) pode ser efetuado da mesma forma que se acede a qualquer lista com o símbolo $. Adicionalmente, dada a sua semelhança estrutural com matrizes, podem usar-­‐se nos data frames os mesmos tipos de indexação que se definiram para as matrizes. Vejam-­‐se alguns exemplos ilustrativos com o data frame anteriormente criado: > df$tamanhos
[1] medio
medio
grande
pequeno grande
pequeno medio
grande
pequeno grande
pequeno medio
grande
Levels: grande medio pequeno
> df$pesos[1:4]
[1] 12 15 35 10
> df[2,2]
[1] medio
Levels: grande medio pequeno
> df[1:3,]
racas tamanhos pesos
1
bulldog
medio
12
2
rafeiro
medio
15
3 doberman
grande
35
> df[,2]
[1] medio
medio
grande
Levels: grande medio pequeno
37 Os mecanismos de indexação do data frame são também semelhantes aos de vetores e matrizes no que diz respeito à aplicação de filtros usando condições ou vetores lógicos. Vejam-­‐se alguns exemplos: > df[df$racas=="bulldog",]
racas tamanhos pesos
1 bulldog
medio
12
5 bulldog
grande
20
> df[df$racas=="bulldog" & df$tamanhos=="medio",]
racas tamanhos pesos
1 bulldog
medio
12
> vl = df$pesos > 12
> vl
[1] FALSE
TRUE
TRUE FALSE
TRUE FALSE
TRUE
TRUE
> df[vl,]
racas tamanhos pesos
2
rafeiro
medio
15
3 doberman
grande
35
5
bulldog
grande
20
7
rafeiro
medio
13
8 doberman
grande
25
Um data frame pode ser editado com a função edit: > df1=edit(df)
Neste caso, podem fazer-­‐se alterações ao data frame df e estas ficarão guardadas no data frame df1. As alterações poderão envolver adicionar novas linhas (novos exemplos), novas variáveis ou simplesmente alterar os valores. Para criar com esta interface um novo data frame deverá iniciar-­‐se o edit com um data.frame vazio: > df2 = edit(data.frame())
O R possui alguns conjuntos de dados internos, que podem ser listados fazendo data(). Cada um destes pode ser carregado usando a função data e indicando o nome do conjunto de dados como argumento. Pode, ainda, listar-­‐se os datasets associados a um determinado package com data(package=”...”). 38 Nos exemplos seguintes, ir-­‐se-­‐á usar o conjunto de dados íris que tem 5 componentes (4 numéricas e 1 fator). Note a utilização da função dim para determinar o tamanho do data frame, da função names para listar os campos e da função head para listas os primeiros elementos do data frame (uma vez que estes são frequentemente de grande dimensão esta função tem bastante utilidade). Dá-­‐se ainda um primeiro exemplo de um filtro sobre o data frame. > data(iris)
> dim(iris)
[1] 150
5
> names(iris)
[1]
"Sepal.Length"
"Sepal.Width"
"Petal.Length"
"Petal.Width"
"Species"
> head(iris)
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
3
4.7
3.2
1.3
0.2
setosa
4
4.6
3.1
1.5
0.2
setosa
5
5.0
3.6
1.4
0.2
setosa
6
5.4
3.9
1.7
0.4
setosa
> iris[iris$Sepal.Length>7.6,]
Sepal.Length Sepal.Width Petal.Length Petal.Width
Species
118
7.7
3.8
6.7
2.2 virginica
119
7.7
2.6
6.9
2.3 virginica
123
7.7
2.8
6.7
2.0 virginica
132
7.9
3.8
6.4
2.0 virginica
136
7.7
3.0
6.1
2.3 virginica
> table(iris$Species)
setosa versicolor
50
virginica
50
50
> tapply(iris$Petal.Length,iris$Species,mean)
setosa versicolor
1.462
4.260
virginica
5.552
Uma forma alternativa de realizar a filtragem de dados é o uso da função subset. A melhor forma de se perceber o seu uso é através de alguns exemplos. > subset(iris, subset=iris$Petal.Length>6.5)
Sepal.Length Sepal.Width Petal.Length Petal.Width
Species
106
7.6
3.0
6.6
2.1 virginica
118
7.7
3.8
6.7
2.2 virginica
119
7.7
2.6
6.9
2.3 virginica
39 123
7.7
>
subset(iris,
2.8
6.7
subset=iris$Petal.Width<0.2,
2.0 virginica
select=c(Species,
Petal.Width))
Species Petal.Width
10
setosa
0.1
13
setosa
0.1
14
setosa
0.1
33
setosa
0.1
38
setosa
0.1
Ao trabalhar repetidamente sobre o mesmo data frame pode tornar-­‐se cansativo usar sempre os nomes completos dos campos com o símbolo $. Uma opção para tornar mais direto o uso de um data frame e sua indexação passa pela operação de attachment. Esta permite usar os campos do data frame como se se tratassem de variáveis. Este processo é iniciado com a função attach e terminado com a função detach. Veja-­‐se um exemplo: > attach(iris)
> Species[1:3]
[1] setosa setosa setosa
Levels: setosa versicolor virginica
> detach(iris)
> Species
Error: object 'Species' not found
3.5 Conversões entre estruturas de dados O R disponibiliza um conjunto de funções que permitem converter objetos de vários tipos em objetos de tipos diferentes, sempre que tal faz sentido. Estas funções têm tipicamente um nome iniciado por as e seguido do tipo para o qual se quer converter. Assim: •
A função as.vector permite converter objetos de vários tipos em vetores; por exemplo pode ser aplicado a uma matriz dando os seus valores de forma linearizada •
A função as.matrix permite converter objetos em matrizes; pode por exemplo ser usada sobre um data frame dando uma matriz numérica se o data frame só tiver campos numéricos ou uma matriz de strings se o data frame tiver strings 40 ou fatores (neste último caso, os valores numéricos são transformados em strings) •
A função as.data.frame permite converter outros objetos em data.frames; um exemplo é a conversão de uma matriz, sendo criado um campo para cada coluna da matriz. Exercícios resolvidos 1. i) Crie uma matriz com 2 linhas e 3 colunas, com os números inteiros de 1 a 6. ii) Identifique a linha 2 da matriz e a coluna 3. iii) Verifique as dimensões da matriz. iv) Calcule a soma dos elementos da matriz. v) Calcule a soma dos elementos da 1ª linha. vi) Crie um vetor cujos elementos sejam as médias dos valores de cada coluna. > mat = matrix(1:6,2,3)
> mat[2,]
[1] 2 4 6
> mat[,3]
[1] 5 6
> dim(mat)
[1] 2 3
> typeof(dim(mat))
[1] "integer"
> class(dim(mat))
[1] "integer"
> sum(mat)
[1] 21
> sum(mat[1,])
[1] 9
> v = c(mean(mat[,1]), mean(mat[,2]), mean(mat[,3]))
> v
[1] 1.5 3.5 5.5
2. i) Defina um vetor com os valores entre 1 e 100. ii) Transforme o vetor anterior numa matriz de dimensões 10x10. 41 iii) Coloque a dimensão da matriz em ii) como c(100). Qual a diferença do vetor que criou em i) e o objeto que tem agora? > uv = 1:100
> dim(uv)= c(10,10)
3. i) Crie um array tri-­‐dimensional com as dimensões 2,5,3 e os trinta primeiros números inteiros. ii) Calcule a soma dos elementos do array cujo valor na segunda dimensão é 3. iii) Calcule a média dos valores que têm nas primeiras duas dimensões 1. iv) Calcule o array que se obtém multiplicando todos os valores do array anterior por 2 e somando a constante 3. > a=array(1:30, c(2,5,3))
> sum(a[,3,])
[1] 93
> mean(a[1,1,])
[1] 11
> a*2+3
4. i) Defina duas matrizes: a matriz A com dimensões 2 x 3 e com o valor 2 em todas as células; a matriz B com dimensões 2 x 3 e os valores entre 1 e 6 ii) Calcule A+ B (com o operador aritmético +) iii) Altere as dimensões da matriz B para que fique com 3 x 2 iv) Calcule a multiplicação de A por B. > A = matrix(2,2,3)
> B = matrix(1:6,2,3)
> A+B
[,1] [,2] [,3]
[1,]
3
5
7
[2,]
4
6
8
> dim(B)=c(3,2)
> A%*%B
[,1] [,2]
[1,]
12
30
[2,]
12
30
42 5. i) Defina uma variável l como uma list com dois campos: nome = “João” e idade = 30. ii) Veja o conteúdo de l, aceda ao campo nome e ao campo idade com $ e [[ ]]. > l = list(nome="Joao",idade=30)
> l
$nome
[1] "Joao"
$idade
[1] 30
> l[[2]]
[1] 30
> l$idade
[1] 30
6. i) Carregue o conjunto de dados pressure. ii) Calcule a média dos valores de pressão (campo pressure) de todos os elementos do conjunto de dados. iii) Verificar qual é a pressão para uma temperatura de 320. iv) Calcular o máximo de pressão que se pode obter com temperaturas menores do que 100. v) Crie um novo data frame com todas as linhas com pressão menor do que 100. > data(pressure)
> mean(pressure$pressure)
> pressure[pressure$temperature==320,2]
> max(pressure[pressure$temperature<100,2])
> pressure[pressure$pressure<100,]
7. Carregue o conjunto de dados sleep. Verifique os tipos de dados de cada campo. Calcule a média do campo extra para um dos grupos (campo group). > data(sleep)
> class(sleep)
> mapply(class,sleep)
> tapply(sleep$extra,sleep$group,mean)
43 4. Leitura e escrita de dados 4.1 Definição da diretoria de trabalho Em cada sessão do R existe sempre uma diretoria (ou pasta) do seu computador que está definida como a diretoria atual de trabalho. Será nesta diretoria que o R, por omissão, procurará os ficheiros para carregar e onde gravará os ficheiros. Note que em cada função de leitura ou escrita das que veremos nas próximas secções, pode definir a localização do seu ficheiro de duas formas: de forma relativa, definindo como base a sua diretoria de trabalho ou de forma absoluta, definindo o caminho completo desde a raiz do sistema de ficheiros . Para saber qual a diretoria de trabalho pode usar a função getwd. Se quiser alterar a sua diretoria de trabalho pode usar a função setwd que recebe como parâmetro a nova diretoria. Em algumas instalações do R (e.g. Windows e Mac OS X) existem opções nos menus da interface gráfica que permitem mudar a diretoria de trabalho. Do mesmo modo, os IDEs (como o Rstudio) permitem realizar esta operação com interfaces próprias. > getwd()
[1] "/Users/miguelrocha"
> setwd("/Users/miguelrocha/analisedados")
O exemplo anterior foi realizado em Mac OS X. Note que em MS Windows, os caminhos usam o símbolo \ no lugar de /. Por outro lado, os caminhos absolutos iniciam com o volume usado (e.g. o disco rígido) sendo algo do tipo: “C:\user\documents”. Note ainda que no exemplo anterior se usaram caminhos absolutos. O mesmo efeito seria obtido com: > setwd("analisedados")
> getwd()
[1] "/Users/miguelrocha/analisedados"
44 4.2 Leitura de dados A forma mais simples de ler dados é recorrer à função scan. Esta permite ler valores diretamente através da linha de comando. Neste caso, o conjunto de valores introduzidos são por omissão lidos como um vetor de valores numéricos introduzidos um a um e terminados por um valor vazio. O argumento what pode ser usado para determinar que o tipo de dados a ler. Por exemplo, para ler um vetor de strings deve usar-­‐se “character”. O argumento file, por seu turno permite que a leitura se faça de um ficheiro. Podemos utilizar a função read.table para ler valores de uma tabela a partir de um ficheiro de texto, assumindo que cada linha tem o mesmo número de elementos. Por exemplo, o comando apresentado a seguir permite ler uma tabela em que os campos estão separados por espaços (ou tabs) e a primeira linha contém o cabeçalho de cada uma das colunas. > tabela = read.table("vendas.txt", header=T)
Note que se assume no exemplo anterior que o ficheiro “vendas.txt” está na pasta de trabalho (ver secção anterior). O argumento header indica se a primeira linha contém o nome dos campos; neste exemplo esse argumento tem o valor T (ou TRUE) o que indica que a primeira linha corresponde a nomes de campos. Esta função tem, para além do já referido, um conjunto alargado de outros argumentos que podem ser consultados fazendo help(read.table). Estes permitem, por exemplo, mudar o separador a ser usado (argumento sep), definir a priori o número de linhas a ler (nrows), o separador decimal (dec), etc. A colocação de file.choose() como ficheiro levará ao aparecimento de uma interface para selecionar o ficheiro do seu sistema de ficheiros. Uma variante da função anterior, que permite ler dados no formato CSV é a função read.csv. Esta define a vírgula como separador e tendo por omissão o valor do argumento header a TRUE. Na realidade, esta função acaba por chamar a anterior com uma definição de argumentos ligeiramente distinta. O resultado de qualquer uma destas funções é um objeto do tipo data.frame (ver secção 3.4) que guarda os dados lidos. > tab = read.csv("vendas.csv")
O R tem diversas funções que permitem ler ficheiros em vários formatos, como seja o caso do formato XLSX (do Microsoft Excel), no formato JSON, no formato XML, etc. 45 Estes não serão cobertos neste texto recomendando-­‐se a leitura da documentação associada às funções read.xlsx e read.xlsx2, para o caso específico do Excel, e das funções file, url e fromJSON para saber como criar conexões e carregar dados. Para realizar a leitura de linhas de texto de um ficheiro ou outra conexão usa-­‐se a função readLines. Assumindo que o ficheiro de interesse para carregar está disponível na internet é, na maioria dos casos, possível fazer o seu download pelos métodos habituais guardando-­‐o numa pasta local. Ainda assim, até para que a reprodutibilidade das suas scripts seja maior, é possível fazer o download dos ficheiros necessários, usando funções R. Neste caso, deve usar-­‐se a função download.file, conforme se pode verificar no exemplo abaixo. >
fileUrl
=
"http://archive.ics.uci.edu/ml/machine-learning-
databases/ecoli/ecoli.data"
> download.file(fileUrl, destfile="ecoli.csv")
> ecoli = read.table("ecoli.csv")
> dim(ecoli)
[1] 336
9
> head(ecoli)
V1
1
V2
V3
V4
V5
V6
V7
V8 V9
AAT_ECOLI 0.49 0.29 0.48 0.5 0.56 0.24 0.35 cp
2 ACEA_ECOLI 0.07 0.40 0.48 0.5 0.54 0.35 0.44 cp
3 ACEK_ECOLI 0.56 0.40 0.48 0.5 0.49 0.37 0.46 cp
4 ACKA_ECOLI 0.59 0.49 0.48 0.5 0.52 0.45 0.36 cp
5
ADI_ECOLI 0.23 0.32 0.48 0.5 0.55 0.25 0.35 cp
6 ALKH_ECOLI 0.67 0.39 0.48 0.5 0.36 0.38 0.46 cp
4.3 Escrita de dados no ecrã e em ficheiro Existem várias formas de podermos imprimir valores no R. A função mais simples é a função print. > x <- c(2, 3, 5, 9)
> print(x)
Mas também podemos utilizar a função cat: > cat("Este é o valor da variável x: ", x, "\n")
46 Neste caso, o comando imprime todos os argumentos que lhe passamos. Repare a string "\n" que lhe passamos no fim para efetuar a mudança de linha. Desta forma, podemos controlar melhor o que queremos imprimir. Este comando permite imprimir o valor para ficheiro. No caso seguinte, a função cria um ficheiro onde guarda o que mandamos imprimir, caso o ficheiro já exista, ele é reescrito. Caso queiramos acrescentar ao ficheiro, temos também que utilizar a opção append. > cat(file = "resultado.txt", "Este é o valor da variável x: ", x,
"\n")
> cat(file = "resultado.txt", "Esta linha é adicionada depois da
linha anterior no mesmo ficheiro\n")
A função str permite imprimir informações sobre a estrutura interna dos objetos sendo bastante útil em particular para objetos com estruturas mais complexas. A função write.table pode ser utilizada para imprimirmos uma tabela (data frame ou matriz), tanto no ecrã como para ficheiro (funcionando como função inversa da read.table). No exemplo a seguir criamos uma matriz 2 por 5 e depois utilizamos os comando a seguir para imprimi-­‐la no ecrã e seguidamente em ficheiro. > x <- matrix(1:10,ncol=5)
> write.table(x)
> write.table(x, file="tabela.txt")
Uma forma simples de conseguir enviar para ficheiro o resultado dos comandos digitados no interpretador é a função sink. Este comando redireciona o resultado dos comandos para ficheiro: > x <- seq(1, 10, 0.1)
> sink("saida.txt")
> x
> x^3
> sink()
O primeiro comando sink indica que o resultado dos comandos (neste caso o cálculo de x e de x ao cubo) será enviado para o ficheiro saida.txt. Caso este ficheiro já 47 exista, ele é recriado (a não ser que se utilize a opção append como se pode ver no exemplo a seguir). > sink("saida.txt", append = T)
> cumsum(x)
> sink()
Neste segundo caso, o somatório cumulativo do vector x é acrescentado ao ficheiro saida.txt. Caso se utilize a opção split, o resultado dos comandos é não só enviado para o ecrã como também para o ficheiro em causa. Assim, no exemplo a seguir, o resultado dos comandos digitados é enviado não só para o ecrã como também acrescentado (por causa da opção append) para o ficheiro. > sink("sai.txt", append=T, split=T)
> summary(x)
> sink()
As funções save e load permitem armazenar em ficheiro os valores associados a certas variáveis e carregá-­‐los mais tarde. > x <- -50:50
> f <- function(x) if(x < 0) 0 else x
> save(x, f, file="dados.Rdata")
E, mais tarde: > load("dados.Rdata")
> f(x)
Repare que a função save indica quais são os objetos que são armazenados. 48 5. Programação de novas funções 5.1 Definição de novas funções O R permite que o utilizador possa definir novas funções que poderão ser utilizadas da mesma forma que as pré-­‐definidas no R. Esta possibilidade torna o sistema R num sistema bem mais flexível e poderoso. O tipo function é um dos tipos de objetos do R podendo ser definido da mesma forma que os outros objetos, usando a atribuição. Para a definição de uma nova função usa-­‐se a palavra chave function. Um exemplo da sua utilização poderá clarificar a forma como se usa: > "quadrado" = function(x) x^2
> quadrado(3)
[1] 9
A primeira linha define uma nova função de nome “quadrado” e que recebe um valor numérico como argumento, retornando o seu quadrado. Na segunda linha a função é invocada e o valor 3 é passado como argumento. As novas funções poderão ser definidas, tal como no exemplo, na linha de comandos, mas poderão também ser definidas num ficheiro de texto exterior e carregadas no R usando a função source. Qualquer editor de texto poderá ser usado para definir um ficheiro a carregar pelo R, existindo inclusive um editor de texto disponível na versão Windows ou Mac OS X do R, que pode ser acedido pela opção “New script” ou “Open Script” do menu “File”. Uma vez definido o ficheiro este poderá ser carregado usando a função source(nome do ficheiro). Na versão Windows esta opção pode também ser efectuada usando a opção “Source R code” no menu File. Como exemplo poder-­‐se-­‐á criar um ficheiro “exemplo.R”, com o seguinte conteúdo: "cubo" = function(x)
{
x^3
}
49 "volume.esfera" = function (r)
# esta função calcula o volume de uma esfera
{
res = 4/3 * pi * cubo(r)
res
}
No código da função volume.esfera, a linha iniciada com o símbolo # representa comentários do programador, i.e. linhas que não são interpretadas pelo R e que apenas servem para melhorar a legibilidade da definição de funções. Note que o resultado de uma função é definido pela expressão dada na última linha da sua definição. Carregando o ficheiro para o R, podem-­‐se invocar as funções definidas: > cubo(9)
[1] 729
> volume.esfera(2)
[1] 33.51032
5.2. Instruções condicionais A instrução if permite realizar um (ou vários) comandos de forma condicional. Assim, se uma dada condição for verdadeira realiza-­‐se um conjunto de operações; se esta for falsa os comandos serão outros (ou nenhuns). A instrução if tem o seguinte formato: if (condição) instrução ou bloco_instruções else instrução ou bloco_instruções Se tivermos mais do que um comando, quer no caso verdadeiro, quer no falso podemos colocar um bloco de instruções. Estes são conjuntos de instruções separados por ; ou mudança de linha e delimitados por { e }. Como exemplo da utilização da função veja-­‐se a definição de uma função que retorna o menor de dois valores numéricos dados como argumentos: "menor.dois" = function (val1, val2)
50 {
if (val1 < val2) res = val1
else res = val2
res
}
Um outro exemplo poderá ser a definição de uma função para calcular o valor absoluto (ou módulo) de um número: "valor.absoluto" = function (val)
{
if (val >= 0) res = val
else res = -val
res
}
5.3 Instruções cíclicas A linguagem de programação do R permite que sejam utilizadas estruturas cíclicas de controlo, quer ao nível da linha de comando, quer ao nível da definição de novas funções. Estes permitem definir uma operação (ou um bloco de operações) que são repetidas mais do que uma vez. A instrução for permite realizar uma operação (ou bloco de operações) um determinado número (fixo) de vezes: for (variável in expressão) instrução for (variável in expressão) { } bloco de instruções Um exemplo simples da utilização da instrução for na linha de comandos: > a = vector()
> for (i in 1:20) a[i]= 2*i
> a
51 [1]
2
4
6
8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
Neste exemplo, é criado um vector e preenchido com todos os números inteiros pares inferiores ou iguais a 40 (de notar que a mesma operação podia facilmente ser conseguida com a função seq). Por outro lado, a instrução for é especialmente utilizada ao nível da definição de novas funções. A função seguinte efetua a soma dos elementos de um vector (de notar que a função sum pode ser usada para a mesma tarefa): “soma.vec” = function (vec)
{
s = 0
for(i in 1:length(vec))
s = s + vec[i]
s
}
Uma forma mais simples de fazer a iteração é a seguinte: “soma.vec” = function (vec)
{
s = 0
for(i in vec) s = s + i
s
}
Um outro exemplo é o seguinte, onde o objectivo é, dada uma matriz como argumento de entrada, criar um vector que tenha como elementos a média de cada coluna. "mediacols" = function (mat)
{
v = vector()
for (i in 1:ncol(mat))
v[i] = mean(mat[,i])
v
}
52 Uma alternativa útil em alguns casos é usar uma instrução cíclica que permite realizar as operações em causa um número variável de iterações. O ciclo while permite essa possibilidade fazendo depender a continuação do ciclo de uma condição que pode tomar o valor verdadeiro ou falso, à semelhança do que acontecia com as instruções condicionais. A sintaxe do ciclo while é a seguinte: while (condição) Instrução ou while (condição) { Bloco de instruções } O bloco de instruções é realizado enquanto a condição se mantiver verdadeira. É de realçar que o bloco de instruções deverá, em algum momento, tornar a condição falsa de forma a que o ciclo termine. Um exemplo de utilização do ciclo while é o seguinte: "descobre" = function (vec, valor)
{
desc = FALSE
i = 1
while (desc == FALSE && i <= length(vec))
{
if (vec[i] == valor) desc = TRUE
else i = i+1
}
desc
}
Neste caso, um vetor é percorrido no sentido de se descobrir se um elemento (valor) existe ou não no vetor. Quando o elemento é descoberto o ciclo while é 53 interrompido. Para evitar que o ciclo se torne infinito, o ciclo termina quando se chega ao final do vetor. É de notar que o facto do R trabalhar essencialmente com vetores, leva a que muitas operações se realizem naturalmente sobre estas estruturas sem necessidade de ciclos. Assim, estas instruções são menos usadas no R do que é normal noutras linguagens de programação. Exercícios resolvidos 1. Escreva e teste uma função que permita fazer uma tabela de conversão entre um valor em euros e o valor correspondente em escudos (recorde que 1 euro = 200,482 escudos). "conv.euros" = function(escudos)
{
res = escudos/ 200.482
res
}
2. Escreva uma função que dadas as coordenadas (x,y) de dois pontos no espaço a duas dimensões retorne a sua distância euclidiana. "distancia" = function(x1, y1, x2, y2)
{
r = (x1-x2)^2 + (y1-y2)^2
sqrt(r)
}
3. Escreva uma função que dado um vetor conte quantos valores são pares. "conta.pares" = function(vect)
{
vl = vect%%2 == 0
nt = sum(vl)
nt
}
54 4. Escreva a função tripeiro que troca os 'v' pelos 'b' e junta ", carago" ao fim das frases. Exemplo: > tripeiro("viva o porto")
[1] "biba o porto, carago"
Resolução (apenas com minúsculas) "tripeiro" = function(frase)
{
fb = gsub("v","b",frase)
paste(fb, ", carago!!")
}
Com minúsculas e maiúsculas: "tripeiro" = function(frase)
{
fb = chartr("vV","bB",frase)
paste(fb, ", carago!!")
}
5. Escrever uma função que dados três números retornem o menor destes (pode usar a função menor.dois se assim o desejar). "menor.tres" = function(val1, val2, val3)
{
r = menor.dois(val1, val2)
menor.dois(val3, r)
}
6. Escrever uma mini-­‐calculadora que receba dois valores numéricos como argumento e uma string (que poderá ser “+”, “-­‐“, “*” ou “/”) e retorne o resultado de respectiva operação aritmética. "calculadora" = function(op1, op2, oper)
{
if (oper == "+") res = op1 + op2
else if (oper == "-") res = op1 - op2
else if (oper == "*") res = op1 * op2
else res = op1 / op2
res
}
55 7. Escreva a função compara que, dados dois valores a e b, retorne 1 se a < b, 0 se a = b e -­‐1 se a > b. "compara" = function(a,b)
{
if (a < b) res = 1
else if (a>b) res = -1
else res = 0
res
}
8. Escreva uma função que dado um vector de valores numéricos retorne a sua média; "media.vec" = function (vec)
{
s = 0
n = 0
for(i in vec)
{
s = s + i
n = n + 1
}
s/n
}
9. Escreva uma função que dado um vector de valores numéricos e um valor limite, retorne o número de valores inferiores a este valor "inferiores.lim" = function(vec, lim)
{
n = 0
for(i in 1:length(vec))
{
if(vec[i] < lim)
n = n + 1
}
n
}
10. Escreva funções (usando ciclos for) que dados vectores com notas de alunos (valores de 0 a 20): 56 a) determinem o número de negativas e de positivas (retornando um vetor); consideram-­‐se como notas positivas as que forem maiores ou iguais a 10. b) determinem a média das notas positivas; "negat.posit" = function (notas)
{
p = 0
n = 0
for (nota in notas)
if (nota >= 10) p = p + 1
else n = n + 1
c(p,n)
}
"media.posit" = function (notas)
{
sp = 0
np = 0
for (nota in notas)
if (nota >= 10) {
np = np + 1
sp = sp + nota
}
sp/np
}
11. Usando ciclos while escreva uma função que: a) dado um vetor de números ordenados por ordem crescente conte quantos elementos são menores do que 10. b) dado um vector determinar qual o número mínimo de elementos que terá que seleccionar de forma a atingir uma determinada soma passada também como parâmetro (sugestão: use a função sort). "menores.10" = function (vec)
{
# assumindo que vec está ordenado
i = 1
while (vec[i] < 10)
i = i + 1
i-1
}
57 12. Usando apenas ciclos for escreva uma função que calcule o maior elemento de uma matriz. "maximo.mat" = function (mat)
{
max = -Inf
for(i in 1:nrow(mat))
for(j in 1:ncol(mat))
if (mat[i,j] > max) max = mat[i,j]
max
}
13. Escreva uma função que dada uma matriz retorne a multiplicação dos valores da sua diagonal. "mult.diag" = function(mat)
{
# assumindo que a matriz é quadrada
md = 1
for(i in 1:nrow(mat))
md = md * mat[i,i]
md
}
14. Escreva uma função que, dadas duas matrizes, dê como resultado uma nova matriz que seja a soma das anteriores. "soma.mat" = function(m1, m2)
{
# assumindo que as duas matrizes tem as mesmas dimensoes
res = matrix(,nrow=nrow(m1),ncol=ncol(m1))
for(i in 1:nrow(m1))
for(j in 1:ncol(m1))
res[i,j] = m1[i,j] + m2[i,j]
res
}
58 6. Sumarização e pré-­‐processamento dos dados 6.1 Introdução Como foi já referido anteriormente, os data frames são as estruturas de dados mais usadas para guardar conjuntos de dados para análise, pela sua capacidade de guardar, numa forma matricial, variáveis numéricas e nominais. Uma das tarefas iniciais e fulcrais da análise de dados é a sua sumarização. De facto, dada a grande dimensão típica dos conjuntos de dados, a melhor forma de poder detectar algum problema e, ao mesmo tempo, ter uma ideia geral da estrutura dos dados passa pela sua sumarização. Entre os diversos problemas que se podem detetar estão incluídos os valores omissos, valores atípicos (outliers), valores fora de intervalos dados, problemas com unidades, variáveis com nomes ou tipos errados, etc. Neste capítulo ir-­‐se-­‐ão abordar funções para sumariar os dados, descobrir alguns dos seus problemas e poder corrigi-­‐
los. De forma a ilustrar algumas destas operações vamos utilizar como exemplo um conjunto de dados de terramotos, do portal norte-­‐americano data.gov. Vejamos como ir buscar e carregar os dados para um data frame eData: >
fileUrl
<-
"http://earthquake.usgs.gov/earthquakes/catalogs/
eqs7day-M1.txt"
> download.file(fileUrl,destfile="earthquakeData.csv")
Content type 'text/plain' length 88093 bytes (86 Kb)
opened URL
==================================================
downloaded 86 Kb
> eData <- read.csv("earthquakeData.csv")
6.2 Verificação da estrutura dos dados Um primeiro conjunto de tarefas que se podem realizar passa pela verificação das dimensões dos dados, i.e. o número de linhas e colunas (com as funções dim, nrow e 59 ncol), dos nomes dos campos e dos exemplos (função names e rownames) e por ter uma ideia dos valores (com a função head). > dim(eData)
[1] 806
10
> nrow(eData)
[1] 806
> ncol(eData)
[1] 10
> names(eData)
[1] "Src"
"Lon"
"Eqid"
"Version"
"Magnitude" "Depth"
"NST"
"Datetime"
"Lat"
"Region"
> rownames(eData)[1:5]
[1] "1"
"2"
"3"
"4"
"5"
> head(eData, 3)
Src
Eqid Version
Lat
1
Datetime
Lon Magnitude Depth NST
ak 10661664
1 Tuesday, February 26, 2013 00:00:25 UTC
62.3348 -148.2315
1.4
2
5
us c000fddz
48.3346
3
Region
-86.8630
50.9
5.5
ak 10661654
1
61.1610 -146.9765
4
Central Alaska
Monday, February 25, 2013 23:37:59 UTC 10.0 262
southern Pacific Ocean
Monday, February 25, 2013 23:17:25 UTC
2.0
8.4
8
Southern Alaska
Podemos ainda aplicar a função class e a função typeof a todos os campos para verificar se correspondem ao que é pretendido. Para aplicar as funções a todos os campos podemos usar a função sapply. > class(eData)
[1] "data.frame"
> sapply(eData[1,],class)
Src
Magnitude
"factor"
Eqid
Version
Depth
"factor"
NST
Datetime
Lon
Region
"factor"
"numeric" "numeric" "integer"
Lat
"factor"
"numeric"
"numeric"
"factor"
> sapply(eData[1,],typeof)
Src
Magnitude
"integer"
"double"
Eqid
Depth
"integer"
Version
NST
"integer"
Datetime
Lat
Lon
"double"
"double"
Region
"integer"
"double" "integer" "integer"
60 Em muitos casos, erros detetados ao nível dos tipos de dados para cada coluna podem ser corrigidos através de uma configuração mais adequada do processo de leitura (e.g. através de parâmetros da função read.table). A correção de valores inadequados ao nível dos nomes dos campos (colunas) e dos exemplos (linhas) pode ser facilmente realizado usando também as funções names e rownames, da forma que se ilustra do exemplo seguinte: > names(eData)
[1] "Src"
"Lon"
"Eqid"
"Version"
"Magnitude” "Depth"
"NST"
"Datetime"
"Lat"
"Region"
> names(eData)[5]="Latitude"
> names(eData)[6]="Longitude"
> names(eData)
[1] "Src"
"Eqid"
"Version"
"Longitude" "Magnitude” "Depth"
"Datetime"
"NST"
"Latitude"
"Region"
> rownames(eData)[1:7]
[1] "1"
"2"
"3"
"4"
"5"
"6"
"7"
> rownames(eData) = paste("id-", rownames(eData), sep="")
> rownames(eData)[1:7]
[1] "id-1"
"id-2"
"id-3"
"id-4"
"id-5"
"id-6"
"id-7"
6.3 Estatística descritiva
Uma outra tarefa importante passa pela análise da distribuição de valores associado a cada uma das variáveis. No que diz respeito a variáveis numéricas, o R apresenta algumas funções que permitem caracterizar os valores de um vetor numérico. Algumas destas foram já apresentadas na secção 2.3 (como o min, max, range, mean, sum). Para além destas outras funções estatísticas podem ser utilizadas. Ao nível das medidas de tendência central, salientam-­‐se as seguintes: •
mean – calcula o valor médio dos valores de um vetor; •
median – calcula a sua mediana No que diz respeito às medidas de variabilidade teremos as seguintes funções: •
sd – calcula o seu desvio padrão •
mad – calcula o desvio absoluto médio 61 IQR – dá o chamado intervalo interquartil, ou seja, a diferença entre os •
valores do quartil 75% e do quartil 25%. Finalmente, existem algumas funções que fazem uma sumarização dos dados indicando vários valores que caraterizam a sua distribuição: quantile – calcula os quartis do conjunto de valores (0% -­‐ mínimo, 25%, •
50% -­‐ mediana, 75%, 100%-­‐ máximo) summary – idêntico mas dá também a média •
Vejamos alguns exemplos com o conjunto de dados eData: > mean(eData$Magnitude)
[1] 2.171464
> median(eData$Magnitude)
[1] 1.7
> sd(eData$Magnitude)
[1] 1.228949
> mad(eData$Magnitude)
[1] 0.7413
> quantile(eData$Magnitude)
0%
25%
50%
75% 100%
1.0
1.3
1.7
2.4
6.1
> summary(eData$Magnitude)
Min. 1st Qu.
1.000
Median
1.300
Mean 3rd Qu.
1.700
2.171
Max.
2.400
6.100
Ao nível das variáveis nominais a principal função que permite verificar a distribuição de valores é a função table, que permite, por um lado dar a distribuição de valores de uma única variável, mas também ver a distribuição de valores quando são consideradas duas variáveis distintas, como exemplificado de seguida: > unique(eData$Src)
[1] ak us nc ci hv uu nn pr nm uw se ld
Levels: ak ci hv ld nc nm nn pr se us uu uw
> table(eData$Src)
ci
hv
252 145
ak
20
ld
nc
nm
nn
pr
2 140
11
45
30
se
us
uu
uw
1 122
22
16
> table(eData$Src,eData$Version)
0
ak
ci
2
3
4
5
6
7
8
9
A
B
C
H
0 127 107
18
0
0
0
0
0
0
0
0
0
0
6
3
0
0
0
0
0
0
0
0
0
43
1
24
69
62 hv
0
11
8
0
1
0
0
0
0
0
0
0
0
0
ld
0
2
0
0
0
0
0
0
0
0
0
0
0
0
nc
50
41
18
22
2
5
0
1
1
0
0
0
0
0
nm
0
0
0
0
0
0
0
0
0
0
8
2
1
0
nn
0
0
0
0
0
0
0
0
0
45
0
0
0
0
pr
30
0
0
0
0
0
0
0
0
0
0
0
0
0
se
0
0
0
0
0
0
0
0
0
0
1
0
0
0
us
0
0
2
9
15
18
26
18
19
8
4
2
0
1
uu
0
0
12
3
6
1
0
0
0
0
0
0
0
0
uw
0
3
3
8
2
0
0
0
0
0
0
0
0
0
6.4 Tratamento de valores omissos Uma questão importante ao nível da exploração inicial dos dados é o tratamento de dados omissos. Como vimos anteriormente, a função is.na permite identificar estes valores e pode ser usada para quantificar a sua presença quer ao nível dos campos, quer ao nível das linhas de um data frame. Por exemplo, as linhas seguintes demonstram que não há valores em falta nos campos que se referem à longitude e à latitude do conjunto de dados que tem vindo a ser usado neste capítulo. Note neste exemplo que a função is.na retorna um vetor de valores lógicos. Ao aplicar a função sum, cada valor TRUE terá o valor de 1 e cada valor de FALSE terá o valor de zero. Logo, o resultado será o número de valores em falta para cada um dos campos. > sum(is.na(eData$Lat))
[1] 0
> sum(is.na(eData$Lon))
[1] 0
Uma distribuição dos valores em falta pode também ser obtida com a função table. Note-­‐se que um dos problemas da existência de valores em falta (NA) em R é que, em alguns casos, as funções devolvem erro quando estes valores ocorrem e noutros eles são pura e simplesmente ignorados. Ambos os comportamentos podem trazer problemas em alguns casos e convém, por isso, estar ciente da existência destes casos. Como exemplo vejamos o comportamento da função table num caso particular ilustrativo: > table(c(0,1,2,3,NA,3,3,2,2,3))
0 1 2 3
63 1 1 3 4
> table(c(0,1,2,3,NA,3,3,2,2,3),useNA="ifany")
0
1
2
3 <NA>
1
1
3
4
1
Como se pode verificar os valores NA são ignorados normalmente pela função table mas tal comportamento pode ser alterado com o argumento useNA. Diversas funções no R permitem este tipo de soluções para o tratamento de dados em falta. No caso da função mean tal pode ser realizado com o argumento na.rm. Existem outros casos de valores erróneos, por exemplo NaN (que significa not a number) que resulta por exemplo de um cálculo indeterminado (e.g. 0/0); estes valores podem ser identificados com função is.nan. Complementarmente, a função complete.cases permite identificar quais as linhas de um data frame têm valores corretos (que não sejam NA nem NaN). Existem várias formas de tratar valores omissos que podem ser mais ou menos adequadas dependendo da análise de dados a realizar. Para além da opção de os manter inalterados e lidar com eles nas diversas funções da análise de dados, existem outras soluções que o R implementa. Uma delas passa por excluir automaticamente as linhas de um data frame que incluam valores omissos, usando a função na.exclude (ou na.omit). Vejamos um exemplo que usa o data frame cfseal do package DAAG: > library(DAAG)
> data(cfseal)
> cfseal$lung
[1]
605.0
436.0
380.0
493.9
NA
550.0
470.0
…
> sum(is.na(cfseal$lung))
[1] 6
> cfseal$lung[cfseal$lung>2000]
[1]
NA
NA
NA
NA 2735
NA 2380
NA
> cfseal$lung[cfseal$lung>2000 & !is.na(cfseal$lung)]
[1] 2735 2380
> mean(cfseal$lung)
[1] NA
> mean(cfseal$lung, na.rm=T)
[1] 1136.846
> cfn = na.exclude(cfseal)
> sum(is.na(cfn$lung))
[1] 0
64 Uma alternativa passa por substituir os valores omissos por outros valores calculados de formas que possam fazer sentido, um processo chamado de imputação. Formas simples incluem a substituição por um valor constante que faça sentido no contexto do problema (como zero, a média ou mediana do campo respetivo), conforme o exemplo seguinte: > sum(is.na(cfseal$lung))
[1] 6
> mean(cfseal$lung)
[1] NA
> m = mean(cfseal$lung, na.rm=T)
> m
[1] 1136.846
> cfseal$lung[is.na(cfseal$lung)] = m
> sum(is.na(cfseal$lung))
[1] 0
> mean(cfseal$lung)
[1] 1136.846
Outros métodos mais elaborados incluem a imputação pelo método dos k vizinhos mais próximos, onde se imputa o valor com base na similaridade de valores para outros atributos conhecidos. Esta operação pode ser realizada com base no package imputation. > library(imputation)
> cfknn = kNNImpute(cfseal,5)$x
[1] "Computing distance matrix..."
[1] "Distance matrix complete"
[1] "Imputing row 5”
…
> mean(cfknn$lung)
[1] 946.4712
6.5 Discretização de variáveis numéricas Uma das operações potencialmente úteis de transformação de variáveis é a discretização de variáveis numéricas, que pode ser usada quando algum tipo de análise apenas pode ser aplicada a variáveis nominais ou queremos simplificar a análise. Esta 65 operação implica dividir o intervalo de valores possíveis em sub-­‐intervalos e considerar cada um deles como uma categoria. A função cut permite transformar vetores numéricos em fatores recebendo como argumentos os valores numéricos e um conjunto de pontos de corte que definem os pontos de fronteira que dividem as várias categorias. Pode ainda receber como argumento os nomes das categorias a criar (vetor de strings). No exemplo seguinte, o vetor de magnitudes do data frame eData é dividido em 4 categorias com pontos de fronteira nos valores 0, 1.5, 2.5, 4 e 7. > range(eData$Magnitude)
[1] 1.0 6.1
>
md
=
cut(eData$Magnitude,
c(0,1.5,2.5,4,7),
labels
=
c("VL","L","M","H"))
> levels(md)
[1] "VL" "L"
"M"
"H"
> table(md)
md
VL
L
M
340 310
H
76 114
> md[1:10]
[1] M
M
M
VL VL VL VL M
L
VL
Levels: VL L M H
> eData$Magnitude = md
> class(eData$Magnitude)
[1] "factor"
6.6 Normalização Em muitos casos, para ser possível comparar variáveis é necessário proceder à normalização dos dados para que estes estejam dentro de valores comparáveis. O processo mais comum de normalizar valores passa pela sua subtração pela média e divisão pelo desvio padrão; ao valor obtido é também dado o nome de z-­‐score. Um processo alternativo usa a mediana e o desvio absoluto médio. A função scale do R permite realizar este processo, tendo como argumentos os dados a normalizar e, opcionalmente duas variáveis lógicas que indicam se os dados são centrados (i.e. subtraídos pela média) e/ ou escalonados (i.e. divididos pelo desvio padrão). Verifique o exemplo seguinte: 66 > mean(iris$Sepal.Length)
[1] 5.843333
> sd(iris$Sepal.Length)
[1] 0.8280661
> sl = as.vector(scale(iris$Sepal.Length))
> mean(sl)
[1] -4.484318e-16
> sd(sl)
[1] 1
Exercícios resolvidos 1. Neste exercício será utilizado o data frame “CO2”. Este data frame regista o consumo de CO2 em plantas de 2 regiões distintas (Mississippi e Quebec) quando expostas a diferentes concentrações de CO2 e quando são sujeitas, ou não, a arrefecimento antes do início da experiência. a) Utilize o comando help para analisar a estrutura do data frame. Veja o conteúdo de cada um dos seus campos com o operador $ e verifique as dimensões do objecto. b) Qual o número de plantas sujeita a tratamento (campo “Treatment”) de arrefecimento (valor “chilled”)? c) Crie um sub-­‐conjunto dos dados que contenha apenas os dados referentes à região “Quebec”. d) Crie uma matriz com os dados de concentração de CO2 e de uptake. Atribua nomes às linhas e colunas, onde para as linhas é usada a identificação da planta e para as colunas as strings “concentr” e “consumo”. e) Considerando a matriz anterior, selecione todos os valores de concentração e consumo referentes à planta com identificação “Mc3”. f) Crie um vector com os valores de consumo, a partir da matriz criada em d). g) Se tivesse um campo com 1000 plantas do tipo “QC3” sujeitas a uma concentração constante de 675 mL/L, qual o consumo de CO2? h) Em que condições o consumo de CO2 é o mais elevado? Como faria para obter a mesma informação mas para cada uma das regiões? i) Qual a média de consumo de CO2 realizado pelas plantas da região “Mississipi” sujeitas a tratamento? # a)
help(CO2)
67 names(CO2)
CO2$Plant
CO2$Treatment
dim(CO2)
# b)
sum(CO2$Treatment =="chilled")
#ou
dim(CO2[CO2$Treatment =="chilled",])[1]
# c)
df = CO2[CO2$Type=="Quebec",]
df
# d)
mat = as.matrix(CO2[c("conc","uptake")])
colnames(mat) = c("concent", "consumo")
rownames(mat) = CO2$Plant
# e)
mat[which(rownames(mat)=="Mc3"),]
# f)
v = mat[,2]
v
# g)
CO2$uptake[CO2$Plant =="Qc3" & CO2$conc == 675]*1000
#ou
index = which(CO2$Plant =="Qc3" & CO2$conc == 675)
val = CO2[index,]$uptake
val*1000
# h)
CO2[which(CO2$uptake == max(CO2$uptake)),]
reg = levels(CO2$Type)
reg
for(r in reg){
ind = which(CO2$uptake == max(CO2$uptake[CO2$Type == r]))
print(r)
print(CO2$uptake[ind])
}
#ou
tapply(CO2$uptake, CO2$Type, max)
# i)
ind = which(CO2$Type =="Mississippi" & CO2$Treatment=="chilled")
mean(CO2$uptake[ind])
# ou
filt = CO2[CO2$Type =="Mississippi" & CO2$Treatment=="chilled",]
mean(filt$uptake)
68 2. Considere a informação constante no ficheiro dados.csv e importe-­‐os para um data frame. a) Explore brevemente o data frame importado. b) Crie um novo data frame que contenha apenas os dados posteriores a 1990 referentes aos estados “Alabama”, “California”, “Florida” e “New York”. c) Referente aos dados obtidos na alínea b) crie um sumário dos dados onde apresente, para casa estado, o número total de crimes por ano. # a)
df3 = read.csv("dados.csv")
dim(df3)
names(df3)
head(df3)
# b)
df3= df3[df3$Year >1990 & df3$State %in% c("Alabama", "California",
"Florida","New York"),]
dim(df3)
# c)
ddply(df3, .(State, Year), summarize, sum = sum(Count))
#ou
states = unique(df3$State)
anos = unique (df3$Year)
vals = c()
for(st in states){
for(ano in anos){
val = sum(df3$Count[df3$Year == ano & df3$State == st])
vals = c(vals, val)
}
}
res = data.frame(states, anos, vals)
res
3. Considere o data frame airquality, que contém dados sobre a qualidade do ar em New York entre Maio e Setembro de 1973. a) Visualize a estrutura do data frame utilizando o comando str. b) Verifique quantos valores dos campos Ozone e Solar.R não estão disponíveis. c) Calcule o valor médio da radiação solar durante o mês de Julho. d) Quais os índices das linhas que têm missing values (NA) no atributo Ozone? e) Crie um novo data frame onde todos os atributos têm valores válidos. f) Para cada um dos meses, calcule a o máximo, mínimo, media, mediana, desvio padrão e variância dos valores de radiação solar. 69 # a)
str(airquality)
# b)
sum(is.na(airquality$Ozone))
sum(is.na(airquality$Solar.R))
# c)
mean(airquality$Ozone[airquality$Month ==7], na.rm=TRUE)
# d)
which(is.na(airquality$Ozone))
# e)
x <- airquality[complete.cases(airquality), ]
#ou
x <- na.omit(airquality)
x
# f)
res = NULL
for(i in 5:9){
dados = x$Solar.R[x$Month ==i]
valores
=
c(max(dados),min(dados),mean(dados),median(dados),sd(dados),var(dado
s))
res = rbind(res, valores)
}
rownames(res) = 5:9
colnames(res) = c("Max","Min","Media","Mediana","Des.P.", "Var.")
res
70 7. Visualização de dados: gráficos em R Neste capítulo, abordaremos algumas das principais funções existentes no R ao nível da visualização de dados através de gráficos. Serão abordadas as funções do package graphics, um dos packages base do R. Outros packages especializados como o lattice ou o ggplot podem ser alternativas, não abordadas neste texto. 7.1 Gráficos de dispersão A função mais simples para construir gráficos é a função plot, que permite construir gráficos de dispersão (ou scatter plots) representando uma (ou várias) variáveis numéricas (ditas dependentes) no eixo dos yy em função de outra no eixo dos xx (dita independente). A função plot recebe como argumentos um vector para o eixo dos xx e um outro vector para o eixo dos yy. O primeiro destes vetores (xx) pode ser omitido assumindo o R que se trata de um vector com a sequência de números inteiros começada em 1 e com comprimento igual ao vector definido para o eixo dos yy. O exemplo seguinte ilustra esta função: > x<-seq(0.2,2,0.01)
> f <- function(x) (log(x))^2-x*exp(-x^3)
> plot(x, f(x))
A função plot pode receber um número considerável de argumentos opcionais, entre os quais podemos destacar: •
type – tipo do gráfico (linhas – “l”, pontos – “p”, ...) •
main – título do gráfico •
xlab, ylab – labels dos eixos x, y •
col – cor(es) •
cex – tamanho dos pontos •
pch – caracter do ponto 71 Caso se queira desenhar uma linha em vez de representar pontos individuais pode usar-­‐se: > plot(x, f(x), type="l")
Vejamos um exemplo usando o data frame iris onde se usam os argumentos col e pch: > plot(iris$Petal.Length, iris$Petal.Width, col="red", pch = 19)
Vejamos um outro exemplo onde se definem as cores dos pontos e o seu tamanho: > plot(iris$Sepal.Length, iris$Sepal.Width, col=iris$Species, pch =
19, cex = iris$Sepal.Length*iris$Sepal.Width*0.1)
72 A função curve pode ser usada para representar gráficos de funções de uma forma mais simples. Vejamos um exemplo simples: > curve(cos(x)/sin(x), -2, 2, col="red")
73 A função pairs permite representar gráficos de dispersão múltiplos entre todos os campos de um data frame, permitindo visualizar as relações entre as variáveis (e.g. quais são mais correlacionadas). Vejamos um exemplo da sua aplicação: > pairs(iris)
Adicionalmente, o R tem alguns packages que permitem a representação de três variáveis. Uma das hipótese é o uso de gráficos de dispersão 3D providenciados pelo package scatterplot3d: > library(scatterplot3d)
>
scatterplot3d(iris$Petal.Width,iris$Sepal.Length,iris$Sepal.Width,
pch=19, color=as.integer(iris$Species))
74 Uma alternativa distinta são os chamados levelplots, que permitem representar três variáveis num gráfico de duas dimensões, onde a côr dos pontos é usada como forma de representar a 3ª variável, conforme o exemplo seguinte: > library(“lattice”)
> levelplot(Petal.Width~Sepal.Length*Sepal.Width, iris)
75 Uma tarefa razoavelmente comum na análise de dados passa pela necessidade de visualizar grandes quantidades de dados. Neste caso, os pontos individuais de um gráfico de dispersão podem ser uma forma confusa, dada a aglomeração de pontos. Pode-­‐se abordar este problema através de métodos de amostragem, i.e. considerando apenas uma parte dos pontos (por exemplo usando a função sample em R, tratada no capítulo 10). Outra abordagem é o uso de outros tipos de gráficos que representam conjuntos de pontos com densidade de cores. Neste âmbito, destaca-­‐se a função smoothScatter, que se ilustra no exemplo seguinte: > x <- rnorm(100000)
> y = rnorm(100000)
> smoothScatter(x,y)
7.2 Boxplots Os boxplots servem para visualizar a distribuição de valores de uma variável numérica mostrando algumas medidas de estatística descritiva de forma gráfica. Num boxplot típico, a mediana é dada por um traço horizontal central, uma zona rectangular 76 cujo lado superior é dado pelo terceiro quartil (Q3) e o inferior pelo primeiro quartil (Q1). Os traços superior e inferior (os chamados bigodes) mostram o valor máximo e mínimo, respetivamente, ou um outro valor limite. No caso da linha superior o seu valor é dado pelo menor dos seguintes valores: o máximo ou o Q3 somado de 1,5 vezes o intervalo inter-­‐quartis (Q3-­‐Q1). Se existirem valores superiores, estes são mostrados por pontos individuais (possíveis outliers). Algo de simétrico acontece na linha inferior. Este tipo de gráficos pode ser construído usando a funçãoo boxplot que permite uma configuração alargada. Veja-­‐se o exemplo seguinte: > boxplot(eData$Magnitude, col="red")
Estes gráficos são sobretudo úteis para comparar distribuições de valores em situações distintas. Podem, por exemplo, ilustrar a distribuição de uma variável dependente de um factor, ajudando a perceber a sua relação. No exemplo seguinte, comparamos a distribuição do comprimento das pétalas em três tipos de flores (usa-­‐se ainda o conjunto de dados iris): > boxplot(iris$Petal.Length ~iris$Species, col = "blue")
77 Vamos fazer o mesmo para outra tabela que já vem com o R. Esta trata da utilização de inseticidas: >
boxplot(count
~
spray,
data
=
InsectSprays,
xlab
=
"Tipo
de
spray", ylab = "Número de Insectos", main = "Dados de Sprays de
Insectos", col="cyan")
78 Podemos usar boxplots para ilustrar os resultados de uma normalização de valores. >
boxplot(
iris$Petal.Length,
iris$Sepal.Length,
col=
c
("blue",
"lightblue"))
> sl = as.vector(scale(iris$Sepal.Length))
> pl = as.vector(scale(iris$Petal.Length))
79 7.3 Histogramas Os histogramas são gráficos bastante simples que permitem caracterizar a distribuição de frequências de valores de uma variável. Nos histogramas, a altura de cada rectângulo é proporcional ao número de observações do intervalo de valores na base. Os histogramas permitem ter uma primeira ideia da distribuição de dados. Por exemplo, pode verificar-­‐se se os dados seguem uma curva “em forma de sino” que indicie uma distribuição normal ou se há enviesamento nos valores. Em R, a construção de histogramas pode ser conseguida através do uso da função hist. Nesta função, os principais parâmetros são o vetor de valores da variável a representar e o número de posições de quebra (breaks). As posições de quebra também podem ser indicadas explicitamente por um vetor. No exemplo seguinte são construídos dois histogramas usando um dos campos do data frame iris, já usado anteriormente. > data(iris)
> hist(iris$Petal.Length, breaks= 30, col="blue")
> hist(iris$Petal.Length, breaks= 0:7, col = "red")
O resultado é mostrado nas figuras seguintes: 80 O exemplo seguinte mostra como podemos calcular uma função de densidade e sobrepor esta, na forma de uma linha a um histograma, usando a mesma variável dos exemplos anteriores: > dens = density(iris$Petal.Length)
> hist(iris$Petal.Length, breaks= 10, xlim=range(dens$x),
ylim =c(0,0.6), probability = T, col = “red”)
> lines(dens, col = “blue”)
81 7.4 Gráficos de barras Os gráficos de barras são uma forma de representar valores numéricos associados a categorias. Podem ser usados para representar frequências de fatores ou para sumariar métricas numéricas em função de categorias. A função barplot permite construir em R gráficos de barras, quer verticais quer horizontais. Alguns exemplos usando conjunto de dados já anteriormente abordados: > barplot(table(iris$Species), col = "blue")
> barplot(tapply(InsectSprays$count, InsectSprays$spray, mean),
col = "orange”, horiz = T)
82 7.5 Gráficos do tipo pie-­‐chart Os gráficos do tipo pie-­‐chart permitem representar frequências de ocorrência de valores distintos de uma variável nominal. Vejamos um exemplo: > pie(table(iris$Species))
83 7.6 Sobreposição de objetos gráficos O R permite que num único gráfico possam ser sobrepostos vários elementos. Existem algumas funções cuja operação sobrepõe elementos sobre um gráfico pré-­‐
existente, construído com a função plot, ou outras funções básicas. Entre estas funções podem destacar-­‐se as seguintes: •
lines – sobrepõe linhas conectando conjunto de pontos dado por segmentos de reta •
abline – linha definida pelo declive e ordenada na origem •
points – sobrepõe outro conjunto de pontos •
legend – permite colocar legendas no gráfico •
text – sobrepõe campos de texto •
symbols – símbolos matemáticos •
axis – textos nos eixos •
polygon – áreas “pintadas” •
curve (usando a opção add = T) Vejamos dois exemplos simples: > plot(iris$Petal.Length, iris$Petal.Width, col=iris$Species, pch =
19)
> lines(rep(median(iris$Petal.Length),2), c(min(iris$Petal.Width),
84 max(iris$Petal.Width)), col = "cyan", lwd=3)
> lines(c(min(iris$Petal.Length), max(iris$Petal.Length)),
rep(median(iris$Petal.Width),2), col= "cyan", lwd=3)
> plot(iris$Petal.Length, iris$Petal.Width, col=iris$Species, pch =
19,
main
=
"Relacao
entre
largura
e
comprimento
das
petalas",
ylab="Largura", xlab="Comprimento")
>
legend(1.5,
2.2,
legend=levels(iris$Species),
col=c("black","red","green"), pch=c(19,19,19))
Um último exemplo mostra-­‐nos como podemos usar estas potencialidades para sobrepor informação sobre mapas, neste caso um mapa mundial interno do R. 85 > library("maps”)
> map("world")
> points(eData$Lon, eData$Lat, pch=19, col="blue")
7.7 Formatação e exportação de gráficos O R permite que se construam painéis com vários gráficos individuais organizados horizontal e/ ou verticalmente. Esta definição é realizada através da função par. Esta função é usada para definir uma grande quantidade de parâmetros gráficos. Neste caso, usaremos o argumento mfrow para definir o número de linhas e colunas para organizar os dados. Por exemplo, se definirmos mfrow = c(2.3), estaremos a organizar um painel com 6 gráficos em duas linhas e três colunas. Esta definição é realizada antes de se definirem os gráficos definindo a sua organização. Os gráficos serão então colocados posição a posição até completarem os espaços disponíveis. Veja-­‐se o exemplo seguinte, onde a função mtext é usada para colocar algum texto acima de cada um dos gráficos individuais: > par(mfrow=c(1,2))
>
hist(iris$Petal.Length,
breaks=
10,
col
=
"red",
xlab
=
"Comprimento petalas", main="")
> mtext(text="a)", side=3, line=1)
>
plot(iris$Petal.Length,
iris$Petal.Width,
col="red",
pch
=
19,
xlab="Comprimento", ylab="Largura", main="")
> mtext(text="b)", side=3, line=1)
86 Uma outra funcionalidade importante do R passa pela possibilidade de gravar os conteúdos de um gráfico (ou um painel de gráficos). Por exemplo, se quisermos guardar as nossas figuras num formato PDF, usa-­‐se a função pdf. Esta é chamada antes de se gerar o gráfico, definindo como argumentos o nome do ficheiro e as dimensões do gráfico (em polegadas). Todos os comandos subsequentes irão gerar o gráfico respetivo sendo o processo terminado com o comando dev.off(). Como exemplo, se quisermos guardar num ficheiro pdf a figura anterior deveríamos executar os seguintes comandos: > pdf(file = “grafico.pdf”, height=4, width=8)
> par(mfrow=c(1,2))
>
hist(iris$Petal.Length,
breaks=
10,
col
=
"red",
xlab
=
"Comprimento petalas", main="")
> mtext(text="a)", side=3, line=1)
>
plot(iris$Petal.Length,
iris$Petal.Width,
col="red",
pch
=
19,
xlab="Comprimento", ylab="Largura", main="")
> mtext(text="b)", side=3, line=1)
> dev.off()
Se quiséssemos usar os formatos PNG, BMP, TIFF ou JPEG, o processo seria semelhante, usando-­‐se as funções png, bmp, tiff e jpeg, respetivamente. Note que nestes casos a unidade usada para as dimensões da imagem serão pixels. 87 Exercícios resolvidos 1. Tendo em consideração os dado airquality, crie um diagrama de caixa (boxplot) para analisar a variação da Radiação solar entre os vários meses. Titulo: “Diagrama de caixas -­‐ Airquality”; Cor das caixas : verde; Texto dos eixos de X e Y : "Meses" e "Rad. Solar (inL angleys)"; Alterar nomes de meses para "Maio","Jun","Jul", "Agos", "Set". > boxplot(airquality$Solar.R~airquality$Month, col='green',
main ="Diagrama de caixas - Airquality",
names=c("Maio","Jun","Jul", "Agos", "Set"),
xlab="Meses", ylab="Rad. Solar (inL angleys)")
2. Crie um gráficos de dispersão que represente a relação entre as variáveis Ozone e Solar.R. Deve filtrar os dados em falta. Titulo: “Ozono vs Radiação Solar”; Eixos: cor azul com números a laranja; Texto dos eixos : modificar cor e tamanho; Usar para o desenho dos pontos “pch =19” explorar outros símbolos; Insira uma linha no ponto médio dos valores de Radiação Solar usando a função ; Desafio: criar um degradé de verde para vermelho, onde a cor verde fica perto da origem. A atribuição de cor deve seguir uma distribuição circular, ou seja, a cor nos pontos (0,a) e (0,a) deve ser a mesma. getColor<- function(x,y){
maximo = max(c(x,y))+1 #+1 devido a arredondamentos
print(maximo)
colors = "c("
for(i in 1:length(x)){
#calculo do raio
r = sqrt(x[i]^2+y[i]^2)
color = r/maximo
#print(r)
#print(paste("color",color))
actualCor
=
eval(parse(text=paste("rgb(",color,",",1-
color,",",0,")")))
colors = paste(colors,"'",actualCor,"',",sep="")
88 }
colors = paste(substr(colors,1,nchar(colors)-1),")",sep="")
colors
}
> dados <- airquality[complete.cases(airquality), ]
> plot(dados$Ozone, dados$Solar.R,
col=eval(parse(text=getColor(dados$Ozone, dados$Solar.R))),
pch =19,
main = "Ozono vs Radiacao Solar",
xlab = "Ozono",
ylab = "",
axes=FALSE
)
> abline(mean(dados$Solar.R),0, col='red')
>
axis(1,
col="blue",
col.ticks="green",
col.axis="orange",
col="blue",
col.ticks="green",
col.axis="orange",
cex.axis=0.8)
>
axis(2,
cex.axis=0.8)
> mtext("Radiacao Solar", side=2, line=3, col="purple", cex=1.5)
3. Coloque na mesma janela, lado a lado, dois gráficos que representem as funções cos e sin no intervalo [–2 , 2]. windows()
par(mfcol=c(1,2))
curve(cos(x),-2,2, col ='green')
curve(sin(x),-2,2, col ='red')
89 8. Redução de dimensionalidade 8.1 Introdução Em muitos casos, os conjuntos de dados a analisar são de dimensões elevadas e as variáveis possuem dependências entre si. Neste capítulo, apresentam-­‐se métodos para reduzir a dimensionalidade dos dados. Estes métodos funcionam identificando conjuntos de variáveis não correlacionadas entre si que explicam a maior parte da variabilidade dos dados. Em termos algébricos estamos interessados em matrizes de rank menor que permitam explicar os dados originais e reconstrui-­‐los de forma o mais aproximada possível. 8.2 Análise de componentes principais A técnica mais popular ao nível da redução de dimensionalidade de dados numéricos é a análise de componentes principais (ou PCA na denominação anglo-­‐
saxónica). A PCA consta de um procedimento algébrico que converte as variáveis originais (que são tipicamente correlacionadas) num conjunto de variáveis não correlacionadas (linearmente) que se designam por componentes principais (PCs) ou variáveis latentes. Assim, a PCA fornece um mapeamento de um espaço com N dimensões (em que N é o número de variáveis originais) para um espaço com M dimensões (onde M é tipicamente muito menor do que N). As PCs são ordenadas pela quantidade decrescente de variabilidade (variância) que explicam. Cada PC é gerada de forma a explicar o máximo de variabilidade da parte ainda não explicada, tendo que ser ortogonal às PCs anteriores. É importante notar que a PCA é sensível à escala dos dados, pelo que se recomenda a sua normalização prévia. A PCA consiste numa decomposição dos dados originais (uma matriz X) em duas matrizes: T.PT. A matriz T tem o nome de scores, indicando as coordenadas dos exemplos iniciais (linhas de X) no novo sistema de coordenadas dado pelas PCs. As PCs determinadas são combinações lineares das variáveis originais, sendo os coeficientes destas no espaço original são dados pelas colunas da matriz P, sendo designados por loadings. Se considerarmos apenas as primeiras k componentes principais, isto implica considerarmos apenas as primeiras k colunas das matrizes T e P, obtendo-­‐se uma 90 aproximação dos dados originais que será tanto mais precisa quanto maior é o valor de k. Há várias formas de realizar a PCA em R, em diversos packages distintos. Uma das funções mais usadas, num dos packages base do R, é a função princomp. Esta tem como argumento obrigatório a matriz de dados ou data frame com os dados originais (necessariamente dados numéricos). Os argumentos opcionais permitem, por exemplo, filtrar linhas ou tratar de formas distintas os valores omissos (argumento na.action). O resultado é uma list, que inclui vários campos com os diversos resultados, incluindo os loadings e os scores. Uma função alternativa é a função prcomp que difere da anterior no método de cálculo da PCA, nos argumentos e na estrutura dos resultados. Por exemplo, esta função permite indicar explicitamente a normalização dos dados com o argumento “scale”. Os exemplos seguintes ilustrarão as principais diferenças na estrutura dos resultados das duas funções. O exemplo seguinte usa o conjunto de dados iris, já usado anteriormente para demonstrar o uso das funções anteriores. Como o conjunto de dados possui 5 atributos, 4 dos quais numéricos, serão apenas estas colunas a serem consideradas (as 4 primeiras). Serão usadas ambas as funções anteriores: > pcares = prcomp(iris[,-5], scale = T)
> pcares2 = princomp(scale(iris[,-5]))
> summary(pcares)
Importance of components:
PC1
Standard deviation
PC2
PC3
PC4
1.7084 0.9560 0.38309 0.14393
Proportion of Variance 0.7296 0.2285 0.03669 0.00518
Cumulative Proportion
0.7296 0.9581 0.99482 1.00000
> summary(pcares2)
Importance of components:
Comp.1
Standard deviation
Comp.2
Comp.3
Comp.4
1.7026571 0.9528572 0.38180950 0.143445939
Proportion of Variance 0.7296245 0.2285076 0.03668922 0.005178709
Cumulative Proportion
0.7296245 0.9581321 0.99482129 1.000000000
> pcares$rotation
PC1
Sepal.Length
Sepal.Width
PC2
0.5210659 -0.37741762
PC3
0.7195664
PC4
0.2612863
-0.2693474 -0.92329566 -0.2443818 -0.1235096
Petal.Length
0.5804131 -0.02449161 -0.1421264 -0.8014492
Petal.Width
0.5648565 -0.06694199 -0.6342727
0.5235971
> pcares2$loadings
Loadings:
91 Comp.1 Comp.2
Comp.3
Comp.4
Sepal.Length 0.521
-0.377
-0.720
Sepal.Width
-0.923
0.244
-0.124
-0.801
-0.269
Petal.Length
0.580
0.142
Petal.Width
0.565
0.634
0.261
0.524
> plot(pcares2)
> biplot(pcares)
Os resultados demonstram que os métodos usadas por ambas as funções obtêm resultados similares mas ligeiramente distintos e que a estrutura das lists de resultados são também diferentes (e.g. a diferença de nomes de loadings para rotation). Os resultados dos gráficos (últimas duas linhas) são mostrados em seguida. 92 8.3 Decomposição em valores singulares A decomposição em valores singulares (SVD em notação anglo-­‐saxónica) é um método algébrico de fatorização de matrizes que pode ser usado em análise de dados para reduzir a dimensionalidade dos dados. De fato, a PCA é um caso particular da SVD, sendo a SVD um dos métodos aconselhados para calcular a PCA (usado pelo R na função prcomp). A SVD consta da fatorização de uma matriz M (de dimensões n x m) em M = UDVT onde: U é uma matriz n x n, V é uma matriz m x m, A tem dimensões n x m; além disso U.UT e V.VT são iguais à matriz identidade de dimensões n e m, respetivamente. As colunas de U são os vetores singulares esquerdos e as de V os vetores singulares direitos. A matriz D é uma matriz diagonal com os valores singulares de M. Em R, a operação de SVD pode ser executada com a função svd. O principal argumento para esta função é a matriz (ou data frame) com dados numéricos, tal como acontece com as funções que realizam a PCA. O resultado é uma list, com três campos: d – matriz diagonal D; u – matriz U; v – matriz V. Note-­‐se que as colunas de v são equivalentes aos loadings resultantes da PCA (se os dados para esta forem normalizados). No exemplo seguinte ilustra-­‐se esta característica com o conjunto de dados usado na secção anterior: > svdres= svd(scale(iris[,-5]))
> svdres$v
[,1]
[1,]
[,2]
[,3]
[,4]
0.5210659 -0.37741762
0.7195664
0.2612863
[2,] -0.2693474 -0.92329566 -0.2443818 -0.1235096
[3,]
0.5804131 -0.02449161 -0.1421264 -0.8014492
[4,]
0.5648565 -0.06694199 -0.6342727
>
plot(pcares$rotation[,1],
0.5235971
svdres$v[,1],
pch=19,
xlab="PC1",
ylab="SV1-dir")
> abline(0,1, col="red")
O gráfico gerado pelas duas últimas linhas do exemplo anterior será o seguinte: 93 No exemplo seguinte demonstra-­‐se a aproximação de uma matriz de dados usando um número diferentes de valores singulares. A primeira linha faz o download do ficheiro (podendo ser substituída por um download direto usando o seu browser) e a segunda linha carrega o ficheiro de dados. A linha seguinte executa o processo de SVD, enquanto a última permite visualizar a componente da variância explicada por cada valor singular. >download.file("https://spark-public.s3.amazonaws.com/dataanalysis/
face.rda", destfile="face.rda")
> load("face.rda")
> svd1 <- svd(scale(faceData))
>
plot(svd1$d^2/sum(svd1$d^2),pch=19,xlab="SV",ylab="Variancia
explicada”)
O gráfico seguinte mostra o resultado da última linha: 94 Nos comandos seguintes mostra-­‐se como podemos criar aproximações aos dados originais usando 1, 5 e 10 valores singulares, respetivamente. Estas aproximações são mostradas em forma de gráfico em seguida, comparando-­‐se com os dados originais (último gráfico). > aprox1 <- svd1$u[,1] %*% t(svd1$v[,1]) * svd1$d[1]
> aprox5 <- svd1$u[,1:5] %*% diag(svd1$d[1:5])%*% t(svd1$v[,1:5])
>
aprox10
<-
svd1$u[,1:10]
%*%
diag(svd1$d[1:10])%*%
t(svd1$v[,1:10])
> par(mfrow=c(1,4))
> image(t(aprox1)[,nrow(aprox1):1])
> image(t(aprox5)[,nrow(aprox5):1])
> image(t(aprox10)[,nrow(aprox10):1])
> image(t(faceData)[,nrow(faceData):1])
95 Exercícios resolvidos 1. Neste exercício será utilizado o data frame “wines” do package kohonen. Este dataset consiste na análise química a 177 amostras de vinho cultivados na mesma região, provenientes de 3 castas. a) Importe os dados do dataset wines do package kohonen e explore o conteúdo das variáveis wine, vintages e wine.classes. Verifique o seu tipo e sumarie os seus dados. b) Utilizando a função prcomp, efectue a análise dos principais componentes dos dados (normalizados). c) Apresente em forma de gráfico o resultado da função anterior e analise quantos componentes serão necessários considerar para explicar 90 % da variabilidade dos dados. d) Proceda ao mesmo tipo de análise utilizando a função princomp. Verifique as diferenças ao nível da estrutura de resultados e ao nível dos valores numéricos calculados (e.g. para os campos rotation / loadings) e) Utilizando SVD calcule as matrizes de scores e loadings para o dataset anterior. (Obs: SVD consta da fatorização de uma matriz M (de dimensões n x m) em M = UDVT. A matrix de loadings corresponde à matriz V e a matriz de scores à matriz U*D. f) Calcule a importância de cada um dos componentes sabendo que é dada pela fracção da variância explicada, isto é, FV = wines.svd$d^2 / sum(wines.svd$d^2), sendo wines.svd o resultado da aplicação da função svd aos dados. g) Apresente três gráficos que representem a variância (wines.svd$d^2 / (nrow(wines) -­‐ 1)), variância relativa (FV ) e a percentagem acumulada da variância total usando a função cumsum. h) Sabendo que a matriz de scores mostra a posição de cada amostra relativamente a cada PC, produza um gráfico que apresente a distribuição dos exemplos considerando os 2 principais componentes PC1 e PC2. Coloque símbolos e cores diferentes para cada classe. i) Usando a função pairs, apresente a posição das amostras considerando os 4 principais componentes (use os atributos pch e col para atribuir símbolos e cores diferentes para cada classe). # a) carregar dados
install.packages("kohonen")
library(kohonen)
data(wines, package = "kohonen")
96 class(wines)
typeof(wines)
colnames(wines)
wines
summary(wines)
class(vintages)
vintages
levels(vintages)
table(vintages)
class(wine.classes)
wine.classes
# b, c)
wines.pca = prcomp(wines, scale = TRUE)
plot(wines.pca, main = "PCA", xlab = "Componentes principais")
summary(wines.pca)
#
verificar
o
campo
Cumulative
Proportion
-
necessarias 8 PCs
# d)
wines.pca2 = princomp(scale(wines))
plot(wines.pca2, main = "PCA")
summary(wines.pca2)
# e)
wines.sc = scale(wines)
wines.svd = svd(wines.sc)
wines.scores = wines.svd$u %*% diag(wines.svd$d)
wines.loadings = wines.svd$v
# f)
wines.vars <- wines.svd$d^2 / (nrow(wines) - 1)
wines.relvars <- wines.vars / sum(wines.vars)
variances <- 100 * round(wines.relvars, digits = 3)
variances[1:5]
wines.relvars2 = wines.svd$d^2 / sum(wines.svd$d^2)
# g)
barplot(wines.vars, main = "Variancias",
names.arg = paste("PC", 1:13))
barplot(wines.relvars, main = "Variancias relativas",
names.arg = paste("PC", 1:13))
barplot(cumsum(100 * wines.relvars),
main = "Variancias acumuladas (%)",
names.arg = paste("PC", 1:13), ylim = c(0, 100))
# h)
plot(wines.scores[,1:2], type = "p",
xlab = paste("PC 1 (", variances[1], "%)", sep = ""),
ylab = paste("PC 2 (", variances[2], "%)", sep = ""),
pch = wine.classes, col = wine.classes)
abline(h = 0, v = 0, col = "gray")
legend(-4, -2, legend = levels(vintages), col = 1:3, pch = 1:3)
97 # i)
pairs(wines.scores[,1:4],pch = wine.classes, col = wine.classes)
98 9. Clustering 9.1 Introdução O objetivo do clustering é o de realizar o agrupamento de entidades / exemplos de um conjunto de dados (as linhas de uma matriz ou data frame) com base na similaridade entre estes. Uma tarefa similar passa pelo agrupamento das colunas (variáveis). Embora não se pretenda neste texto abordar em detalhe as variantes, as formulações e os algoritmos para realizar esta tarefa note-­‐se que as abordagens para este problemas variam sobretudo na forma como se define a similaridade, nos algoritmos usados para realizar o agrupamento e na forma de visualizar e interpretar os resultados. Neste texto, abordaremos duas abordagens distintas, o clustering hierárquico e o clustering k-­‐means, focando na forma como estas se podem implementar com a linguagem R. 9.2 Clustering hierárquico O clustering hierárquico tem como principal característica o tipo de resultados que gera. Neste caso, o resultado final do processo é uma árvore binária que representa possíveis divisões dos dados em clusters. Assim, na raiz todos os dados estão agrupados num único cluster, e ao descer na árvore os clusters vão-­‐se dividindo de forma binária, ou seja, em cada nó da árvore são criados dois clusters pela divisão de um único. O método mais usado para construir estas árvores é designado por aglomerativo, pois inicia-­‐se com um cluster para cada exemplo (folhas da árvore). Em cada iteração, vão-­‐se juntando dois clusters e criando um único (criando nós na árvore) até se atingir o ponto em que todos os clusters estão unidos num único. O processo é baseado numa matriz de distâncias onde estão guardadas as distâncias entre todos os pares de objetos; esta matriz é construída aplicando uma métrica de similaridade sobre as linhas da matriz inicial de dados. Esta pode ser, por exemplo, baseada em métricas clássicas de distância como a distância euclidiana ou a distância de Manhattan. 99 Para obter a matriz de distâncias em R, pode usar-­‐se a função dist. Esta recebe como argumento uma matriz ou um data frame com valores numéricos, retornando a matriz de distâncias. Por omissão, o método usado para o cálculo das distâncias é a distância euclidiana, mas podem ser escolhidas outras opções como a distância de Manhattan ou a distãncia de Minkowski. O exemplo seguinte mostrar como calcular a matriz de distâncias para um exemplo com pontos gerados de forma aleatória formando três clusters naturais. Note que o resultado é uma matriz triangular dado que as matrizes de distâncias são simétricas. > x = rnorm(12,mean=rep(1:3,each=4),sd=0.2)
> y = rnorm(12,mean=rep(c(1,2,1),each=4),sd=0.2)
> dataFrame <- data.frame(x=x,y=y)
> dist(dataFrame)
1
2
3
4
5
6
7
8
9
2
0.3412
3
0.5749 0.2410
4
0.2638 0.5258 0.7186
5
1.6942 1.3582 1.1195 1.8067
6
1.6581 1.3196 1.0833 1.7808 0.0815
7
1.4982 1.1662 0.9256 1.6013 0.2111 0.2166
8
1.9915 1.6909 1.4564 2.0284 0.6170 0.6979 0.6506
9
2.1363 1.8317 1.6783 2.3567 1.1834 1.1150 1.2858 1.7646
10
11
10 2.0642 1.7699 1.6310 2.2923 1.2384 1.1655 1.3206 1.8351 0.1409
11 2.1470 1.8518 1.7107 2.3746 1.2815 1.2107 1.3736 1.8699 0.1162
0.0831
12 2.0566 1.7466 1.5865 2.2723 1.0770 1.0077 1.1774 1.6622 0.1084
0.1912 0.2080
Para executar o processo de clustering hierárquico usa-­‐se a função hclust. Esta recebe como argumento a matriz com as distâncias. Como argumento opcional (method), a função permite escolher a forma como se calculam distâncias entre clusters com mais do que um ponto nos passos intermédios do algoritmo. Os valores possíveis para esta opção incluem “complete” (valor por omissão, indica que a distância entre 2 clusters é a maior distância entre qualquer par de elementos dos 2 clusters), “single” (indica que a distância entre 2 clusters é a menor distância entre qualquer par de elementos dos 2 clusters) e “average” (indica que a distância entre 2 clusters é a média das distância entre todos os pares de elementos dos 2 clusters). Veja-­‐se um exemplo com os dados do exemplo anterior: 100 > distxy = dist(dataFrame, method = "euclidean")
> hc = hclust(distxy)
> plot(hc)
O resultado seria o seguinte: Uma representação gráfica dos dados relacionada com o clustering hierárquico são os heatmaps, que podem ser construídos em R com a função heatmap. Esta permite representar os dados como uma imagem onde cada valor da matriz de dados é representado com uma célula cuja cor varia consoante o valor respetivo, num gradiente de cores que pode ser configurado. Os heatmaps tipicamente incluem as árvores criadas por clustering hierárquico quer ao nível das linhas, quer ao nível das colunas de dados. Note o exemplo seguinte: > x1 <- rnorm(12,mean=rep(1:3,each=4),sd=0.2)
> x2 <- rnorm(12,mean=rep(1:3,each=4),sd=0.2)
> y1 <- rnorm(12,mean=rep(c(1,2,1),each=4),sd=0.2)
> y2 <- rnorm(12,mean=rep(c(1,2,1),each=4),sd=0.2)
> df2 = data.frame(x1, x2, y1, y2)
> heatmap(as.matrix(df2))
Cuja representação gráfica é a seguinte: 101 9.3 Clustering k-­‐means O problema de clustering k-­‐means constitui uma das possíveis formulações do clustering, onde o objetivo é o de minimizar a média do quadrado das distâncias de cada ponto ao centro do cluster a que pertence. Nesta formulação, o número de clusters é dado como parâmetro de entrada (designado por k). Dado que este problema apresenta uma significativa complexidade (dentro da classe dos problemas NP-­‐completos), métodos heurísticos são tipicamente usados na resolução do problema. A função kmeans permite resolver um problema de clustering k-­‐means dado um conjunto de dados e o valor de k (parâmetro centers). Veja um exemplo de seguida, usando os dados criados na secção anterior: > resKmeans <- kmeans(dataFrame, centers=3)
> resKmeans$cluster
[1] 2 2 2 2 3 3 3 3 1 1 1 1
>
plot(dataFrame$x,dataFrame$y,
col=resKmeans$cluster,
pch=19,
cex=2)
> points(resKmeans$centers, col=1:3, pch=3, cex=3, lwd=3)
O resultado da última linha será o gráfico seguinte: 102 Exercícios resolvidos 1. Neste grupo de exercícios vamos considerar um subgrupo das amostras presentes do dataset wines do package kohonen, já trabalhado no capítulo anterior. a) Como primeiro passo, retire 30 amostras de forma aleatória. b) Calcula as matrizes de distâncias, entre as várias amostras, considerando vários tipos de distância: euclidiana, manhattan e máxima c) Utilizando clustering hierárquico agrupe as amostras considerando as distâncias anteriormente calculadas. Teste a função com vários métodos ( "single", "complete" e "average"). Visualize os resultados de cada opção. Tente verificar até que ponto o clustering realizado está de acordo com as classes presentes na variável vintages. d) Considere a função kmeans do R para agrupar os dados do dataset wines em 3 grupos. Verifique quantas amostras foram colocadas num grupo diferente do real (assumindo a classificação dada pela variável vintages). # a)
library(kohonen)
data(wines, package = "kohonen")
indexes = sample(nrow(wines), 30)
newData = wines.sc[indexes,]
# b)
newData.Euclidean = dist(newData)
103 newData.Maximum = dist(newData, method ="maximum")
newData.Manhattan = dist(newData, method ="manhattan")
# c)
newData.hcsingle <- hclust(newData.Euclidean, method = "single")
plot(newData.hcsingle, labels = vintages[indexes])
newData.hccomplete <- hclust(newData.Euclidean, method = "complete")
plot(newData.hccomplete, labels = vintages[indexes])
newData.hcsingle <- hclust(newData.Manhattan, method = "single")
plot(newData.hcsingle, labels = vintages[indexes])
newData.hccomplete <- hclust(newData.Manhattan, method = "complete")
plot(newData.hccomplete, labels = vintages[indexes])
newData.hccomplete <- hclust(newData.Manhattan, method = "average")
plot(newData.hccomplete, labels = vintages[indexes])
# d)
wines.km = kmeans(wines.sc, 3)
table(vintages, wines.km$cluster)
104 10. Inferência/ modelação estatística Neste capítulo abordaremos algumas das principais potencialidades da ferramenta R ao nível da realização de tarefas de inferência e modelação estatística. Serão cobertas as principais funções para lidar com as mais importantes distribuições estatísticas e alguns dos testes estatísticos mais usados. 10.1 Distribuições de probabilidade em R O R possui uma estratégia integrada de tratamento das diversas distribuições de probabilidade através de quatro funções principais para cada distribuição: •
Funções iniciadas por d: dão a função de densidade de probabilidade •
Funções iniciadas por p: dão a função acumulativa de probabilidade •
Funções iniciadas por r: geram números aleatórios com base na distribuição •
Funções iniciadas por q: dão os quantis da distribuição Estas funções estão disponíveis para as distribuições mais usadas, nomeadamente para as seguintes (o conjunto não de modo nenhum exaustivo): •
Ditribuição uniforme: funções dunif, punif, runif, qunif •
Distribuição binomial: funções dbinom, pbinom, rbinom, qbinom •
Distribuição de Poison: funções dpois, ppois, rpois, qpois •
Distribuição normal: funções dnorm, pnorm, rnorm, qnorm •
Distribuição exponencial: funções dexp, pexp, rexp, qexp •
Distribuição qui-­‐quadrado: funções dchisq, pchisq, rchisq, qchisq •
Distribuição t de student: funções dt, pt, rt, qt Em seguida, apresentam-­‐se alguns exemplos simples do uso destas funções. O primeiro destes diz respeito à distribuição uniforme mostrando a geração de 1000 valores aleatórios gerados no intervalo [0,1[, comparando-­‐se o seu histograma com a função de probabilidade teórica. > u = runif(1000)
105 > hist(u, prob=T)
> curve(dunif(x,0,1),add=T, col="red")
> summary(u)
Min.
1st Qu.
Median
Mean
3rd Qu.
Max.
0.001214 0.236300 0.456100 0.482600 0.725700 0.999300
O exemplo seguinte diz respeito à distribuição binomial. Neste caso, é gerado um primeiro conjunto de dez variáveis binomiais com 1 tentativa, no seguinte dez variáveis com 10 tentativas e no último com 100 variáveis binomiais também com 10 tentativas. Neste último caso, calcula-­‐se a média e a variância, mostrando-­‐se o histograma e a respetiva distribuição teórica. > rbinom(10, 1, 0.5)
[1] 1 1 0 0 1 1 1 1 1 1
> rbinom(10, 10, 0.5)
[1] 3 5 6 5 3 5 4 5 6 8
> b = rbinom(100, 10, 0.3)
> mean(b)
[1] 2.82
> var(b)
[1] 1.846061
> hist(b, prob = T)
> points(0:10,dbinom(0:10,10,0.3),type="h",lwd=3, col= "red")
> points(0:10,dbinom(0:10,10,0.3),type="p",lwd=3, col= "red")
No exemplo seguinte apresentam-­‐se alguns exemplos respeitantes à distribuição de Poison. Neste caso, são gerados 20 valores de uma distribuição com média 20 e, em 106 seguida, 1000 valores com média 10, calculando-­‐se a sua média, variância e desvio padrão, verificando-­‐se que estão de acordo com os valores teóricos. Finalmente, são dados alguns exemplos do uso da função acumulada, calculando a probabilidade de valores inferiores a 8, e os terceiro e primeiro quartis (dada uma média 10). > rpois(20, 5)
[1] 9 2 6 2 3 6 3 4 6 4 2 6 4 5 5 5 3 8 3 5
> p = rpois(1000, 10)
> mean(p)
[1] 9.973
> var(p)
[1] 9.539811
> sd(p)
[1] 3.088658
> ppois(8, 10)
[1] 0.3328197
> qpois(0.75,10)
[1] 12
> qpois(0.25,10)
[1] 8
No exemplo seguinte, são apresentadas algumas funções para trabalhar com a distribuição normal, apresentando-­‐se a geração de 100 valores de uma distribuição padrão N(0,1) e de outra N(10,3), o cálculo de algumas métricas sobre estes, o cálculo da probabilidade acumulada para o valor 1 e do quantil para o valor 97.5% (numa distribuição padrão). As últimas duas linhas mostram o histograma dos primeiros valores gerados e uma curva com a distribuição de probabilidade teórica. > x = rnorm(100)
> mean(x)
[1] 0.00897715
> sd(x)
[1] 1.081045
> y = rnorm(100, mean=10, sd = 3)
> mean(y)
[1] 9.70692
> sd(y)
[1] 2.927611
> pnorm(1)
[1] 0.8413447
> qnorm(0.975)
107 [1] 1.959964
> hist(x,probability=T,col=gray(.9), ylim=c(0,0.45))
> curve(dnorm(x),-5,5, add=T, col="red")
De uma forma semelhante, o exemplo seguinte mostra como gerar pontos de uma distribuição exponencial, calcular a sua média e representar graficamente os pontos gerados e a função de probabilidade teórica. > x=rexp(100,1/2500)
> mean(x)
[1] 2603.688
> hist(x,probability=TRUE,col=gray(.9))
> curve(dexp(x,1/2500),add=T, col="red", lwd = 3)
108 Um último exemplo mostra como se pode verificar a aproximação da distribuição t de student à distribuição normal à medida que se aumenta o número de graus de liberdade. > curve(dnorm, -5, 5, col="blue")
> curve(dt(x, df=30), -5, 5, col="red", add=T)
> curve(dt(x, df=10), -5, 5, col="orange", add=T)
> curve(dt(x, df=1), -5, 5, col="purple", add=T)
10.2 Verificar graficamente a normalidade dos dados Dada a sua importância em vários tipos de análise de dados, determinar a conformidade de um conjunto de dados com a distribuição normal é uma tarefa essencial na análise de dados. Uma primeira aproximação visual a esta tarefa pode passar pela representação dos dados através do uso de histogramas. Uma alternativa mais precisa, ainda que visual, passa pela visualização dos quantis dos dados de interesse versus os quantis da distribuição teórica, com os chamados QQ plots. Em R, estes gráficos são construídos pela função qqnorm, complementada pelo uso da função qqline. Nos exemplos que se seguem mostram-­‐se exemplos com dados 109 gerados por uma distribuição normal e por uma distribuição uniforme. Como seria de esperar o ajuste visual do primeiro caso é muito melhor do que no segundo. Note ainda que no primeiro caso este ajuste não é comprometido pelo fato de os pontos provirem de uma distribuição normal não padrão, o que se justifica pelo fato de esta representação ser independente da escala. > x = rnorm(100, 10, 5)
> qqnorm(x,main='normal(10,5)', pch=19, col="blue")
> qqline(x, col="red", lwd=3)
> x = runif(100)
> qqnorm(x,main='uniforme(0,1)', pch=19, col="blue")
> qqline(x, col="red", lwd=3)
110 Note que se pode construir um gráfico semelhante comparando duas amostras, usando a função mais genérica qqplot. 10.3 Amostragens Em muitas situações, quando pretendemos realizar diversas operações de simulação e inferência estatística há a necessidade de realizarmos amostragens de partes de um conjunto de dados. Existem duas formas distintas de realizar a amostragem: com reposição (i.e. o valor selecionado é recolocado nos dados e pode ser escolhido de novo) ou sem reposição. A função mais imediata do R para realizar este tipo de tarefas é a função sample. Esta função recebe um vetor com o conjunto total de dados e o número de items a escolher. O argumento “replace” permite definir se a amostragem é feita com ou sem amostragem (T ou F, respetivamente; por omissão F). Podem ainda ser dadas probabilidades para a escolha de cada item, sendo que por omissão todos têm a mesma possibilidade de escolha. Nos exemplos seguintes mostram-­‐se algumas aplicações simples desta função: > sample(1:6, 5, replace = T)
[1] 3 6 5 3 3
> sample(c("cara","coroa"),4,replace=TRUE)
[1] "cara"
"coroa" "cara"
"cara"
111 > sample(1:49, 6)
[1] 16
2 43 27 13 33
> cartas = paste(rep(c("A",2:10,"J","Q","K"),4), c("C","P","E","O"),
sep="")
> sample(cartas, 3)
[1] "2C" "QP" "4E”
> sample(1:10,10)
[1]
6
8
2 10
9
3
4
7
1
5
No primeiro caso, simula-­‐se o lançamento de um dado por 5 vezes; no segundo, o lançamento de uma moeda por 4 vezes; no terceiro, a escolha de seis números entre 1 e 49 (e.g. uma chave de totoloto); no quarto, a escolha de 3 cartas distintas de um baralho; finalmente, no último, gera-­‐se uma permutação aleatória dos valores entre 1 e 10. 10.4 Intervalos de confiança Uma das tarefas essenciais na análise de dados passa pela necessidade de estimar parâmetros de uma distribuição a partir dos dados (e.g. estimar a média de uma distribuição, assumindo que é normal). A partir dos dados conhecidos (amostra) é normalmente impossível saber com certeza os valores dos parâmetros, mas podemos chegar a estimativas dos seus valores, bem como quantificar a incerteza. Assim, os intervalos de confiança constituem uma estimativa, dada por um intervalo de valores que contém o parâmetro procurado com um dado nível de confiança. Talvez o caso mais comum seja o da estimação do intervalo de confiança para a média de uma população, com base nos valores de uma amostra. No caso geral, não conhecemos o desvio padrão da amostra e este cálculo pode ser realizado usando os cálculos em R que se mostram em seguida. Neste caso, assume-­‐se um nível de confiança de 95% sendo usada a distribuição t de student com n-­‐1 graus de liberdade, sendo n o tamanho da amostra (neste caso n = 10). > x = c(175,176,173,175,174,173,173,176,173,179)
> mean(x)
[1] 174.7
> se = sd(x) / sqrt(10)
> se
[1] 0.6155395
> qt(0.975,9)
112 [1] 2.262157
> intconf = mean(x) + c(-se*qt(0.975,9), se*qt(0.975,9))
> intconf
[1] 173.3076 176.0924
Se o valor de n for suficientemente grande (e.g. maior do que 30) pode usar-­‐se a distribuição normal no lugar da distribuição t. Os cálculos anteriores podem ser substituídos pelo uso da função t.test, que se explicará em mais detalhe na próxima secção. No caso de pretendermos calcular intervalos de confiança para uma proporção numa dada população, e dada uma estimativa dessa proporção numa amostra, o intervalo de confiança pode ser calculado com o código R seguinte, onde suc representa o número de exemplos “positivos” e n o número total de exemplos na amostra: > suc = 42
> n = 100
> se = sqrt( (suc/n) * ((n-suc)/n) / n )
> se
[1] 0.04935585
> intconf = c(suc/n - zstar*se, suc/n + zstar*se)
> intconf
[1] 0.3232643 0.5167357
Este intervalo pode ser obtido com a função prop.test que veremos na secção seguinte. 10.5 Testes estatísticos Os testes estatísticos são usados para podermos verificar uma hipótese relacionada com a distribuição dos dados, dada uma amostra. Neste caso, geramos uma hipótese H0 (dita hipótese nula) sobre a distribuição e depois calculamos uma estatística de teste que nos permite calcular a probabilidade de podermos rejeitar esta hipótese. O principal resultado da maioria dos testes é o chamado p-­‐value. Este valor diz-­‐
nos a probabilidade de obtermos o valor calculado da estimativa ou um valor pior, se assumirmos a hipótese nula. Valores suficientemente pequenos permitem assim rejeitar a hipótese nula. Um valor é considerado suficientemente pequeno se for inferior a 1 (100%) subtraído pelo nível de significância (confiança) assumido. Há uma relação 113 forte entre os testes estatísticos e a determinação de intervalos de confiança, pelo que iremos verificar que as funções que permitem obter ambas as informações são as mesmas. Os testes estatísticos podem dividir-­‐se em dois grandes grupos: paramétricos e não paramétricos. Os primeiros dizem respeito a hipóteses sobre parâmetros de uma distribuição estatística, e assim sendo, fazem assumpções explícitas sobre a distribuição dos dados, por exemplo assumindo a distribuição normal dos mesmos. Os testes não paramétricos não impõem qualquer distribuição assumida nos dados não assumindo o cálculo de qualquer parâmetro. Um dos testes mais utilizados é o teste à média de uma distribuição. Neste caso, tendo o conjunto de valores de uma variável, a hipótese nula consiste em afirmar que a média é igual a um determinado valor pré-­‐definido. No caso de se poder assumir que a variável segue uma distribuição, o teste a realizar será um teste paramétrico. A função R que permite efetuar este teste é a função t.test, que recebe como argumento obrigatório os dados aos quais o teste será aplicado. Um dos argumentos opcionais (alternative) permite indicar se o teste é realizado considerando como hipótese alternativa o teste nos dois sentidos (maior e menor; valor “two-­‐sided”, usado por omissão) ou apenas num deles (valores “less” ou “greater”). O parâmetro “mu” permite definir qual o valor pré-­‐definido para a média, considerado zero por omissão. Como resultado, a função calcula o valor da estimativa (neste caso a estimativa t que segue uma distribuição t de student) e o respetivo valor de p-­‐value. Para além disso, esta função calcula ainda o intervalo de confiança associado à média. Por omissão, o nível de confiança é de 95%, podendo este valor ser alterado através do argumento “conf.level”. Nos exemplos seguintes mostra-­‐se a aplicação deste teste. No primeiro caso, a média tomada por omissão é zero. Neste caso, o p-­‐value calculado é muito baixo (próximo de zero) permitindo rejeitar a hipótese nula, dado que a média é significativamente diferente de zero. No exemplo seguinte, com os mesmos dados, a média pré-­‐definida é de 175 e, neste caso, o valor de p-­‐value é elevado não permitindo rejeitar a hipótese nula. Note que neste caso o valor pré-­‐definido para a média está incluído no intervalo de confiança gerado. > x = c(175,176,173,175,174,173,173,176,173,179)
> t.test(x)
One Sample t-test
data:
x
t = 283.8161, df = 9, p-value < 2.2e-16
114 alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
173.3076 176.0924
sample estimates:
mean of x
174.7
> t.test(x, mu=175)
One Sample t-test
data:
x
t = -0.4874, df = 9, p-value = 0.6376
alternative hypothesis: true mean is not equal to 175
95 percent confidence interval:
173.3076 176.0924
sample estimates:
mean of x
174.7
Note ainda que o resultado deste teste é um objeto da classe “htest”, que pode ser usado para obter os principais resultados, usando-­‐se uma notação semelhante a uma list, conforme exemplo abaixo. Isto permite a re-­‐utilização dos valores destes testes em código subsequente. > t.t = t.test (x)
> class(t.t)
[1] "htest"
> names(t.t)
[1]
"statistic"
"estimate"
"parameter"
"null.value"
[8] "method"
"p.value"
"conf.int"
"alternative"
"data.name"
> t.t$p.value
[1] 4.259313e-19
> t.t$conf.int
[1] 173.3076 176.0924
attr(,"conf.level")
[1] 0.95
> attr(t.t$conf.int, "conf.level")
[1] 0.95
A mesma função pode ainda ser utilizada quando o objetivo é o de comparar os valores de duas variáveis, realizando assim a comparação de duas amostras distintas visando determinar se a sua média é igual (será esta a hipótese nula). 115 No exemplo seguinte, mostra-­‐se a aplicação da função t.test neste contexto. Note neste exemplo o uso do argumento “var.equal” com o valor TRUE indicando que se assume que as amostras possuem variância idêntica. Neste caso, o valor de p-­‐value não permite rejeitar a hipótese nula, pelo que as médias poderão ser idênticas. > drug = c(15, 10, 13, 7, 9, 8, 21, 9, 14, 8)
> placebo = c(15, 14, 12, 8, 14, 7, 16, 10, 15, 12)
> t.test(drug, placebo, alt="less", var.equal=TRUE)
Two Sample t-test
data:
drug and placebo
t = -0.5331, df = 18, p-value = 0.3002
alternative hypothesis: true difference in means is less than 0
95 percent confidence interval:
-Inf 2.027436
sample estimates:
mean of x mean of y
11.4
12.3
Um outro exemplo é apresentado de seguida mostrando como realizar este teste quando as amostras são emparelhadas, ou seja, há uma relação entre cada valor de cada uma delas. Neste caso, usa-­‐se o argumento “paired” para indicar este caso. O resultado de p-­‐value permite rejeitar a hipótese nula com um nível de confiança acima de 99%. > diag_med1 = c(3, 0, 5, 2, 5, 5, 5, 4, 4, 5)
> diag_med2 = c(2, 1, 4, 1, 4, 3, 3, 2, 3, 5)
> t.test(diag_med1, diag_med2, paired=T)
Paired t-test
data:
diag_med1 and diag_med2
t = 3.3541, df = 9, p-value = 0.008468
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
0.325555 1.674445
sample estimates:
mean of the differences
No caso de não se poder garantir a normalidade dos dados, os testes não paramétricos terão que ser usados. Neste caso, realizam-­‐se testes à mediana da distribuição, nomeadamente o teste de Wilcoxon, cuja execução pode ser realizada em R correndo a função wilcox.test. Um exemplo é mostrado de seguida, considerando dados já usados com a função t.test. 116 > wilcox.test(x, conf.int=T)
Wilcoxon signed rank test with continuity correction
data:
x
V = 55, p-value = 0.005541
alternative hypothesis: true location is not equal to 0
95 percent confidence interval:
173 176
sample estimates:
(pseudo)median
174.5
Note-­‐se que a normalidade dos dados pode ser determinada, para além do método gráfico já atrás enunciado, correndo testes estatísticos, como por exemplo o teste de Shapiro. Este teste pode ser realizado com a função shapiro.test, sendo dados dois exemplos em seguida. Note que a hipótese nula é a de que os dados seguem a distribuição normal, sendo rejeitada apenas no segundo caso. > n = rnorm(100)
> shapiro.test(n)
Shapiro-Wilk normality test
data:
n
W = 0.9864, p-value = 0.396
> n = runif(100)
> shapiro.test(n)
Shapiro-Wilk normality test
data:
n
W = 0.9546, p-value = 0.001685
No caso de pretendermos realizar testes às proporções poderemos usar a função prop.test. Esta permite ainda calcular os respetivos intervalos de confiança. Mostram-­‐se em seguida dois exemplos. No primeiro caso, testa-­‐se se dados 420 casos positivos em 1000, será possível rejeitar a hipótese nula de que a proporção na população é de 0,5. Neste caso, é possível rejeitar a hipótese nula. > prop.test(420,1000,p=.5)
1-sample proportions test with continuity correction
data:
420 out of 1000, null probability 0.5
X-squared = 25.281, df = 1, p-value = 4.956e-07
alternative hypothesis: true p is not equal to 0.5
95 percent confidence interval:
0.3892796 0.4513427
sample estimates:
117 p
0.42
No caso seguinte, é realizado um teste considerando duas amostras, testando a hipótese nula de as proporções serem iguais. Neste caso, não é possível rejeitar a hipótese nula. > prop.test(c(45,56),c(45+35,56+47))
2-sample test for equality of proportions …
data:
c(45, 56) out of c(45 + 35, 56 + 47)
X-squared = 0.0108, df = 1, p-value = 0.9172
alternative hypothesis: two.sided
95 percent confidence interval:
-0.1374478
0.1750692
sample estimates:
prop 1
prop 2
0.5625000 0.5436893
Por outro lado, o teste paramétrico de qui-­‐quadrado permite verificar o ajuste de um conjunto de dados a uma determinada distribuição ou comparar duas amostras verificando se provêm da mesma distribuição. A função chisq.test permite realizar este teste. Vejamos alguns exemplos: no primeiro caso, testa-­‐se se a frequência de obtenção de cada valor num dado pode provir de uma distribuição uniforme onde cada valor tem probabilidade de 1/6. Dado que o p-­‐value não permite rejeitar a hipótese nula, os resultados são consistentes com a hipótese inicial. > freq_dados = c(22,21,22,27,22,36)
> probs = rep(1/6,6)
> probs
[1] 0.1666667 0.1666667 0.1666667 0.1666667 0.1666667 0.1666667
> chisq.test(freq_dados, p=probs)
Chi-squared test for given probabilities
data:
freq_dados
X-squared = 6.72, df = 5, p-value = 0.2423
No caso seguinte, a mesma função é usada para verificar se dois conjuntos de valores podem provir da mesma distribuição. No caso, geram-­‐se valores com um dado correto e um outro adulterado. Os resultados do teste permitem rejeitar a hipótese nula e, assim, comprovar que os valores não provêm da mesma distribuição. 118 > dado.bom = sample(1:6,200,p=c(1,1,1,1,1,1)/6,replace=T)
> dado.mau = sample(1:6,100,p=c(.5,.5,1,1,1,2)/6,replace=T)
> res.bom = table(dado.bom)
> res.mau = table(dado.mau)
> res.bom
1
2
3
4
5
6
36 26 30 38 33 37
> res.mau
1
2
9
3
4
5
6
9 24 14 11 33
> chisq.test(rbind(res.bom, res.mau))
Pearson's Chi-squared test
data:
rbind(res.bom, res.mau)
X-squared = 15.858, df = 5, p-value = 0.007262
O teste de Kolmogorov-­‐Smirnov constitui uma alternativa não paramétrica ao anterior para o caso em que os valores podem não seguir uma distribuição normal. Neste caso, usa-­‐se a função ks.test. Os dois exemplos seguintes demonstram a sua utilização. No primeiro caso, pode concluir-­‐se pela rejeição da hipótese nula, o que permite afirmar que os dados não seguem uma distribuição normal. > ks.test(as.vector(scale(iris$Petal.Length)), "pnorm")
One-sample Kolmogorov-Smirnov test
data:
as.vector(scale(iris$Petal.Length))
D = 0.1982, p-value = 1.532e-05
alternative hypothesis: two-sided
No segundo caso, temos um exemplo de teste a duas amostras onde se testa de dois conjuntos de valores podem provir da mesma distribuição, sendo neste caso a hipótese rejeitada. > ks.test ( as.vector ( scale ( iris$Petal.Length ) ),
as.vector (
scale ( iris$Sepal.Width ) ) )
Two-sample Kolmogorov-Smirnov test
data:
as.vector(scale(iris$Petal.Length))
and
as.vector(scale(iris$Sepal.Width))
D = 0.22, p-value = 0.001406
alternative hypothesis: two-sided
119 10.6 Análise de variância A análise de variância (ANOVA) constitui um conjunto de modelos estatísticos e testes associados que pretende particionar a variância de cada observação nos diversos componentes atribuídos a cada fonte de variação. Na sua versão mais simples, pretende analisar se a média de vários grupos de observações é a mesma, constituindo de alguma forma uma generalização dos testes às médias para o caso de mais do que duas amostras. Nesta caso, a ANOVA é realizada como um teste estatístico, assumindo que os dados seguem a distribuição normal, cuja hipótese nula é a de que as médias de cada um dos grupos são iguais. É usada a estatística F que compara a variabilidade entre cada grupo com a variabilidade entre grupos, seguindo a distribuição F de Fisher. Vejamos em seguida um exemplo de como aplicar esta técnica usando a função oneway.test. Neste caso, temos 3 amostras e o teste não permite rejeitar a hipótese nula, não se provando diferenças significativas nas médias dos 3 grupos. > prof1 = c(4,3,4,5,2,3,4,5)
> prof2 = c(4,4,5,5,4,5,4,4)
> prof3 = c(3,4,2,4,5,5,4,4)
> notas = data.frame(prof1, prof2, prof3)
> notas_st = stack(notas)
> notas_st
values
ind
1
4 prof1
2
3 prof1
…
16
4 prof2
17
3 prof3
…
> oneway.test(values ~ ind, data=notas_st, var.equal=T)
One-way analysis of means
data:
values and ind
F = 1.1308, num df = 2, denom df = 21, p-value = 0.3417
Uma análise mais detalhada às variâncias pode ser obtida com a função aov. A sua utilização e os seus resultados ilustram-­‐se em seguida para o mesmo exemplo. > anv = aov(values ~ ind, data=notas_st)
120 > anv
Call:
aov(formula = values ~ ind, data = notas_st)
Terms:
ind Residuals
Sum of Squares
Deg. of Freedom
1.75
16.25
2
21
Residual standard error: 0.8796644
Estimated effects may be unbalanced
> summary(anv)
Df
ind
2
Residuals 21
Sum Sq
Mean Sq
F value
Pr(>F)
1.75
0.8750
1.131
0.342
16.25
0.7738
O mesmo tipo de análise pode ser efetuada considerando a variável dependente como um fator, como se mostra no exemplo seguinte: > anvf = aov(iris$Petal.Length ~ iris$Species)
> summary(anvf)
Df Sum Sq Mean Sq F value Pr(>F)
iris$Species
Residuals
2
437.1
218.55
147
27.2
0.19
1180 <2e-16 ***
--Signif. codes:
0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Neste caso, pode realizar-­‐se com base nestes resultados o teste de Tukey para identificar pares de grupos com médias significativamente diferentes entre si. Os resultados da aplicação da função TukeyHSD aos dados do exemplo anterior mostram-­‐
se em seguida. Neste caso, todos os pares de valores do grupo Species mostram diferenças significativas (p-­‐values iguais a zero). > TukeyHSD(anvf)
Tukey multiple comparisons of means
95% family-wise confidence level
Fit: aov(formula = iris$Petal.Length ~ iris$Species)
$`iris$Species`
diff
lwr
upr p adj
versicolor-setosa
2.798 2.59422 3.00178
0
virginica-setosa
4.090 3.88622 4.29378
0
virginica-versicolor 1.292 1.08822 1.49578
0
121 Podem ainda considerar-­‐se casos em que as variáveis independentes são múltiplas, sendo neste caso a técnica designada por ANOVA multifatorial. Vejamos alguns exemplos em seguida. >
anv2
=
aov(iris$Petal.Length
~
iris$Petal.Width
+
iris$Sepal.Length)
> summary(anv2)
Df Sum Sq Mean Sq F value
iris$Petal.Width
1
430.5
iris$Sepal.Length
1
9.9
9.9
147
23.9
0.2
Residuals
430.5 2647.53
Pr(>F)
< 2e-16 ***
61.15 9.41e-13 ***
--Signif. codes:
0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
> anv3 = aov(iris$Petal.Length ~ iris$Species + iris$Sepal.Length)
> summary(anv3)
Df Sum Sq Mean Sq F value Pr(>F)
iris$Species
2
437.1
218.55
2737.2 <2e-16 ***
iris$Sepal.Length
1
15.6
15.57
194.9 <2e-16 ***
146
11.7
0.08
Residuals
--Signif. codes:
0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Finalmente, quando os dados não seguirem uma distribuição normal, a alternativa passa pelo uso do teste de Kruskal-­‐ Wallis. Este é implementado pela função kruskal.test do R. O seu uso é demonstrado pelo exemplo seguinte. > kruskal.test(values ~ ind, data= notas_st)
Kruskal-Wallis rank sum test
data:
values by ind
Kruskal-Wallis chi-squared = 1.9387, df = 2, p-value = 0.3793
Exercícios resolvidos 1. Considere os dados do data frame wines do package kohonen e implemente em R testes estatísticos que permitam realizar as seguintes tarefas: 122 a. Comparar os valores da coluna “alcohol” da matriz wines, entre os grupos “Barolo” e “Barbera”. Será que podemos concluir que as duas castas têm teores alcoólicos distintos com base neste teste ? b. Comparar os valores da coluna “alcohol” da matriz wines, entre os três grupos de castas. Realize a análise de variância deste teste. O que conclui? c.
Complete a análise da alínea anterior realizando testes entre cada par de grupos de castas. Compare os resultados que obteve com o boxplot. d. Verifique de que forma a variável o grau alcoólico (“alcohol”) pode variar em função da variabilidade das variáveis “malic acid”, “flavonoids” e “tot. phenols”. # a)
subconj = vintages %in% c("Barolo","Barbera")
boxplot(wines[subconj, "alcohol"] ~ vintages[subconj])
# permite verificar que variancias sao similares
t.test(wines[subconj, "alcohol"] ~ vintages[subconj], var.equal = T)
#
p.value
proximo
de
zero
->
permite
rejeitar
hipotese
nula
->
medias diferentes
# b)
boxplot(wines[, "alcohol"] ~ vintages)
oneway.test(wines[,"alcohol"] ~vintages, var.equal = T)
anv = aov(wines[,"alcohol"] ~vintages)
anv
summary(anv)
anv$coef
# c)
TukeyHSD(anv)
boxplot(wines[, "alcohol"] ~ vintages)
# d)
anv2
=
aov(wines[,"alcohol"]
~
wines[,"malic
acid"]
+
wines[,"flavonoids"] + wines[,"tot. phenols"])
anv2
anv2$coef
summary(anv2)
123 A. Casos práticos A1. As laranjas Enunciado
1. Crie um vector de números inteiros de 1 a n (número da laranja). 2. Crie um vector de n strings, representando as categorias das laranjas. Os valores possíveis serão “A”, “B”, “C” ou “D”. Para o efeito: a) Defina uma função geraCategoria que crie um número aleatório entre 0 e 1 e depois conforme o seu valor crie uma variável do tipo string que seja “A”, “B”, “C” ou “D” dependendo do valor desta variável. Cada valor deverá ter igual probabilidade de ser escolhido. b) Defina uma função geraCategorias(n) que crie um vetor de n elementos (strings) que podem ser “A”, “B”, “C” ou “D”. Use a função criada em a) para gerar cada elemento do vetor. 3. Crie um vetor de n valores aleatórios gerados entre 7,5 e 12,5. Este será o vector de diâmetros das laranjas. 4. Crie um vetor de n valores aleatórios gerados entre 80 e 120. Este será o vector de pesos das laranjas. 5. Crie um vetor com os volumes das n laranjas. Para cada laranja deverá usar a fórmula de cálculo do volume de uma esfera. Implemente essa função. 6. Crie um vetor de classificações das laranjas em “sumo” ou “venda”, de acordo com a regra: “sumo”, se a divisão do peso pelo diâmetro for superior a 0,25; “venda” no caso oposto. Use uma ou mais funções para realizar esta tarefa. 7. Calcule a média, a mediana, o desvio padrão, o máximo e o mínimo dos pesos e volumes das laranjas. 8. Calcule o nº de laranjas de sumo e venda (Sugestão: use a função table). 9. Usando a função data.frame, crie um data frame com os vectores criados em 1 a 6. 10. Calcule a média dos diâmetros de cada categoria de laranjas (A, B, C, D). 11. Calcule a média dos pesos de cada classificação de laranjas (sumo ou venda). 124 Resolução
> n=100
> numeros = 1:n
> categorias = gera.categorias(100)
> diametros = runif(100,7.5,12.5)
> pesos = runif(100,80,120)
> volumes = mapply(volume.esfera, diametros/2)
> mean(volumes)
> mean(pesos)
> median(pesos)
> median(volumes)
> sd(pesos)
> sd(volumes)
> range(volumes)
> range(pesos)
> table(classes)
>
df
=
data.frame(nums=numeros,
diams=diametros,
vols
=
volumes,
pesos= pesos, cats= categorias, classes=classes)
> tapply(df$diams, df$cats, mean)
> tapply(df$pesos, df$classes, mean)
"gera.categoria" = function()
{
r = runif(1)
if (r < 0.25) res = "A"
else if (r < 0.5) res = "B"
else if (r < 0.75) res = "C"
else res = "D"
res
}
"gera.categorias" = function (n)
{
v = vector()
for (i in 1:n)
v[i] = gera.categoria()
v
}
"volume.esfera" = function (raio)
{
res = 4/3 * pi * raio ^3
res
125 }
"classifica" = function (pesos, vols)
{
v = vector()
for (i in 1:length(pesos))
if (pesos[i] / vols[i] > 0.25) v[i] = "sumo"
else v[i] = "venda"
v
}
A2. Processo biológico Enunciado
Considere a seguinte tabela de dados provenientes de um processo biológico de fermentação: Tempo (horas) Peso do reator (kgs) Biomassa ac. (gr) Acetato (gr) 0 3 5 0 1 3,1 5,5 0 2 3,2 6,2 0 3 3,35 7 0,1 4 3,5 8,1 0,2 5 3,7 9,4 0,4 6 3,95 10,9 0,65 7 4,2 12,3 0,95 8 4,45 14,1 0,7 9 4,7 16,4 0,45 10 5 18,9 0,25 1. Crie vetores para guardar os valores de cada uma das colunas anteriores. 2. Calcule a média do valor do acetato ao longo da reação. 3. Crie um gráfico com o tempo nos eixo dos xx e a biomassa no eixo dos yy. Faça um gráfico de linhas com o título “Evolucao da Biomassa ao longo do tempo”. 126 4. Calcule o valor ganho em termos de biomassa em cada intervalo de tempo (subtraindo o valor para o tempo t pelo valor para o tempo t-­‐1). Faça uma pequena função que realize esta tarefa. 5. Crie um vetor de strings, com o nome “regime”, do tamanho dos anteriores, que tenha o valor “F” caso o acetato seja menor do que 0.5 e “R” no caso contrário. Faça uma pequena função que realize esta tarefa. 6. Crie um data frame com os vectores criados em 1 e em 5. 7. Usando o data frame anterior calcule a média do acetato nos regimes “R” e “F”, respectivamente. Resolução
1. > tempo = 0:10
> peso = c(3, 3.1, 3.2, 3.35, 3.5, 3.7, 3.95, 4.2, 4.45, 4.7, 5)
> biomassa = c(5, 5.5, 6.2, 7, 8.1, 9.4, 10.9, 12.3, 14.1, 16.4,
18.9)
> acetato = c(rep(0,3), 0.1, 0.2, 0.4, 0.65, 0.95, 0.7, 0.45, 0.25)
2. > mean(acetato)
3. >
plot(tempo,
biomassa,
type="l",
main="Evolucao
da
biomassa
ao
longo do tempo")
4. ganhos = function(biomassa)
{
res = vector()
for(i in 1:length(biomassa)-1)
{
res[i] = biomassa[i+1] - biomassa[i]
}
res
}
5. regime = function(acetato)
127 {
res = vector()
for(i in 1:length(acetato) )
{
if (acetato[i] < 0.5) res[i] = "F"
else res[i] = "R"
}
res
}
6. > df = data.frame(tempo, pesos, biomassa, acetato, regimes)
7. > tapply(df$acetato, df$regimes, mean)
128 

Documentos relacionados