Link para o material da apresentação.

Transcrição

Link para o material da apresentação.
Mineração e Visualização de Dados usando Java
1
Mineração e Visualização de Dados usando Java
Sumário
1 Introdução
1.1
2
Sobre este documento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
2 Weka Informações Gerais
3
3 Weka Representação de Dados
3.1
Criando Relações (arquivos .ARFF)
6
em Aplicações em Java . . . . . . . . . . . . . . . . . . . . .
4 Weka Árvores de Decisão
6
9
4.1
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
4.2
Usando Árvores de Decisão em Aplicações em Java . . . . . . . . . . . . . . . . . . . . . . . . .
9
5 Weka Redes Neurais
13
Multilayer Perceptron
5.1
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
5.2
Usando Redes Neurais Multilayer Perceptron em Aplicações em Java . . . . . . . . . . . . . . . .
13
6 Weka Agrupamento com
16
K-Means
6.1
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
6.2
Usando o Algoritmo K-Means em Aplicações em Java . . . . . . . . . . . . . . . . . . . . . . . .
16
7 Visualização usando Coordenadas Paralelas
18
7.1
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
7.2
Implementação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
7.3
Possíveis melhorias
29
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8 Visualização de Grafos com JUNG
30
8.1
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
8.2
Implementação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
Referências
Rafael Santos
47
http://www.lac.inpe.br/∼rafael.santos
2
Mineração e Visualização de Dados usando Java
1
Introdução
1.1
Sobre este documento
Este documento é um guia prático para o desenvolvimento de aplicações usando a linguagem Java juntamente com
algumas APIs (Application Programming Interfaces) como Weka (Waikato Environment for Knowledge Analysis) [14] e JUNG (Java Universal Network/Graph Framework) [12]. O documento mostra como integrar estas APIs
em código Java, possibilitando o desenvolvimento de aplicações especializadas para resolver problemas especícos.
Este documento
.
não é:
Um tutorial sobre mineração e visualização de dados em geral tutoriais e muitas referências para estes
tópicos podem ser encontradas em [9] e [11].
.
Um tutorial sobre o uso dos ambientes grácos interativos do Weka (Explorer, Experimenter e Knowledge
Flow).
.
Um tutorial sobre a linguagem Java e suas APIs, orientação a objetos ou programação com interfaces
grácas para isto veja [8] e [10].
.
Um tutorial sobre o IDE (Integrated Development Environment, ambiente de desenvolvimento integrado)
Eclipse, sugerido para o desenvolvimento.
Esta versão do documento foi reescrita e adaptada para uso no treinamento Mineração de Dados e
Aplicações para o Centro de Tecnologia da Informação Renato Archer CTI, em Fevereiro/Março de
2010.
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
2
3
Weka Informações Gerais
Muitos algoritmos podem ser usados para mineração de dados, e vários destes são relativamente clássicos e
bem conhecidos, porém sua implementação correta pode ser muito complexa ou custosa.
Pesquisadores da
Universidade de Waikato, na Nova Zelândia, desenvolveram um pacote de software chamado Weka (Waikato
Environment for Knowledge Analysis) [13, 14] que contém uma aplicação com interface gráca onde um usuário
pode experimentar com os diversos algoritmos presentes no pacote para tentar extrair conhecimento de seus dados.
Figura 1: Interface gráca principal do ambiente Weka.
Figura 2: Tela da aplicação Explorer, parte do ambiente Weka.
Além da interface gráca para exploração, os algoritmos do pacote podem ser executados a partir de linha de
comando e embutidos em aplicações em Java. Os próximos capítulos deste documento mostram como representar
dados no Weka em um formato padrão e que pode ser facilmente construído e como usar algoritmos do Weka
em suas aplicações.
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
4
Mineração e Visualização de Dados usando Java
Figura 3: Tela da aplicação Explorer, mostrando um experimento em classicação.
Figura 4: Tela da aplicação Explorer, mostrando um experimento em visualização simples.
Para usar as classes da API Weka é necessário copiar o arquivo
weka.jar
para um diretório conhecido (ou no
classpath da sua instalaçào Java) e referenciar este arquivo em seu projeto. Para fazer isto usando a IDE Eclipse,
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
5
siga estes passos:
1. Crie um diretório
use o nome
lib).
lib
no seu projeto (clique no projeto com o botão direito do mouse e em
2. Copie para este diretório o arquivo
a tecla
F5
weka.jar.
New/Folder,
Ao terminar de copiar, selecione o nome do projeto e clique
para atualizar os subdiretórios relacionados ao projeto.
3. Inclua este arquivo no projeto selecionando o projeto, clicando com o botão direito do mouse e escolhendo
Build Path/Configure Build Path. Clique no botão Add JARs e selecione no diálogo o diretório lib
e dentro dele o arquivo weka.jar. Feche todos os diálogos clicando em seus respectivos botões OK.
Outras IDEs devem ser conguradas com outros métodos. Consulte a documentação da sua IDE para informações
especícas.
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
6
Mineração e Visualização de Dados usando Java
3
Weka Representação de Dados
Em alguns exemplos deste tutorial usaremos arquivos do tipo
diretamente pela API Weka. Um arquivo no formato
.ARFF
.ARFF (Attribute-Relation File Format), suportados
é um arquivo de texto puro, que pode ser editado
por qualquer ferramenta básica de edição de texto sem formatação. O conteúdo do arquivo é composto de três
partes:
. Relação,
a primeira linha do arquivo, que deve ser igual a
@relation
seguida de uma palavra-chave que
identique a relação ou tarefa sendo estudada.
. Atributos,
um conjunto de linhas onde cada uma inicia com
@attribute
seguida do nome do atributo
e seguida do seu tipo, que pode ser nominal (neste caso as alternativas devem aparecer como uma lista
separada por vírgulas e cercadas por chaves) ou numérico (neste caso o nome deve ser seguido da palavrachave
real).
Geralmente, em uma tarefa de classicação supervisionada (onde conhecemos as classes das
instâncias usadas para treinamento) o último atributo é a classe para as instâncias e deve ser nominal.
. Dados,
depois de uma linha contendo
@data.
Cada linha deve corresponder a uma instância e deve ter
valores separados por vírgula correspondentes (e na mesma ordem) dos atributos da seção Atributos.
O arquivo também pode conter linhas iniciadas com o sinal de percentagem (%). Estas linhas serão consideradas
comentários e não serão processadas. Mais informações sobre o formato (inclusive como criar arquivos de forma
programática) podem ser lidas em
http://weka.wikispaces.com/ARFF.
Como um exemplo, o conteúdo do arquivo
weather.arff
é mostrado na listagem 1.
Este arquivo contém
relações (descritores e dados) para uma tarefa simples de previsão de atividade esportiva em função do tempo.
Listagem 1: O arquivo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
weather.arff,
que contém relações sobre um problema jogar em função do tempo.
@relation weather
@attribute
@attribute
@attribute
@attribute
@attribute
outlook { sunny , overcast , rainy }
temperature real
humidity real
windy { TRUE , FALSE }
play { yes , no }
@data
sunny ,85 ,85 , FALSE , no
sunny ,80 ,90 , TRUE , no
overcast ,83 ,86 , FALSE , yes
rainy ,70 ,96 , FALSE , yes
rainy ,68 ,80 , FALSE , yes
rainy ,65 ,70 , TRUE , no
overcast ,64 ,65 , TRUE , yes
sunny ,72 ,95 , FALSE , no
sunny ,69 ,70 , FALSE , yes
rainy ,75 ,80 , FALSE , yes
sunny ,75 ,70 , TRUE , yes
overcast ,72 ,90 , TRUE , yes
overcast ,81 ,75 , FALSE , yes
rainy ,71 ,91 , TRUE , no
3.1
Criando Relações (arquivos
.ARFF) em Aplicações em Java
.ARFF tem estrutura simples, e por ser arquivos de texto, podem facilmente ser editados em editores
Por exemplo, podemos converter arquivos .CSV (comma-separated value) para .ARFF com a inclusão
Os arquivos
de texto.
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
7
de um cabeçalho adequado.
.ARFF é através da criação de sua estrutura dentro de
.ARFF é uma coleção de instâncias onde cada uma é uma
Outra forma de criar arquivos
uma aplicação em Java.
Uma relação em um arquivo
coleção de atributos. Os
passos para criar uma uma base de dados com a estrutura esperada para ser armazenada como um arquivo
.ARFF
sãosão os seguintes:
1. Criamos os atributos usados na base de dados (instâncias da classe
Attribute).
Atributos numéricos são
criados com uma chamada ao construtor da classe usando somente o nome do atributo como argumento.
Atributos discretos ou nominais devem ser criados usando o construtor que recebe o nome do atributo e
uma lista de valores (rótulos) que podem ser usados para aquele atributo. Esta lista deve ser uma instância
de
FastVector,
uma implementação interna do Weka similar à classe
devemos usar uma instância de
2. Criamos uma lista (novamente
instância de
FastVector
igual à
FastVector)
null.
dos atributos.
Vector.
Para atributos textuais
Usamos a lista de atributos para criar uma
Instances, passando para o construtor da classe um nome para a relação, a lista de atributos
Instances corresponde à relação contida em um arquivo .ARFF.
e uma capacidade inicial. Esta classe de
3. Com a instância de
Instances
podemos criar várias instâncias de
Instance
(no singular), onde cada
uma corresponde a somente uma entrada na base de dados (linha da tabela), e popular esta instância de
Instance
4. O método
com os dados. Devemos também associar a instância de
Instance
à de
Instances.
toString da classe Instances pode ser usado para convertê-la para uma representação textual
.ARFF.
que pode ser armazenada em um arquivo
Estes passos são demonstrados pela classe
Listagem 2: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
CreateArtificialARFF,
CreateArtificialARFF,
que é mostrada na listagem 2.
que cria uma relação de dados para uso com o Weka.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import weka . core .*;
/* *
* Esta classe cria uma relação artificial de dados , demonstrando como criar a estrutura
* de um arquivo . ARFF usando Java .
*/
public class CreateArtificialARFF
{
public static void main ( String [] args )
{
// Primeiro precisamos criar os atributos para a relação . Atributos nominais são
// criados já com os valores permitidos ; atributos reais não .
// Nome : String ( sem valores prévios ).
Attribute nome = new Attribute (" nome " ,( FastVector ) null );
// Profissão : nominal com valores prévios .
FastVector vProfissões = new FastVector ();
vProfissões . addElement (" Professor " );
vProfissões . addElement (" Bolsista " );
vProfissões . addElement (" Motorista " );
vProfissões . addElement (" Médico " );
vProfissões . addElement (" Pesquisador " );
Attribute profissão = new Attribute (" profissao " , vProfissões );
// Saldo médio mensal : real .
Attribute saldo = new Attribute (" saldo " );
// Idade : real .
Attribute idade = new Attribute (" idade " );
// Anos como correntista : real .
Attribute anosConta = new Attribute (" anosComoCorrentista " );
// Já fez empréstimo : nominal com valores prévios .
FastVector vSN = new FastVector ();
vSN . addElement (" SIM " );
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
8
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Mineração e Visualização de Dados usando Java
vSN . addElement (" NÃO " );
Attribute empréstimoPrévio = new Attribute (" emprestimoPrevio " , vSN );
// Plano de investimentos sugerido : nominal com valores prévios .
FastVector vPIS = new FastVector ();
vPIS . addElement (" Prata " );
vPIS . addElement (" Ouro " );
vPIS . addElement (" Platina " );
Attribute planoSugerido = new Attribute (" planoSugerido " , vPIS );
// Com todos os atributos podemos criar a relação .
FastVector atributos = new FastVector ();
atributos . addElement ( nome );
atributos . addElement ( profissão );
atributos . addElement ( saldo );
atributos . addElement ( idade );
atributos . addElement ( anosConta );
atributos . addElement ( empréstimoPrévio );
atributos . addElement ( planoSugerido );
// Criamos a instância de Instances
Instances correntistas = new Instances (" correntistas " , atributos ,0);
// Agora podemos preencher a relação com instâncias .
Instance i1 = new Instance ( correntistas . numAttributes ());
i1 . setValue ( nome ," Sid Sackson " );
i1 . setValue ( profissão ," Médico " );
i1 . setValue ( saldo ,4300.0);
i1 . setValue ( idade ,48);
i1 . setValue ( anosConta ,7);
i1 . setValue ( empréstimoPrévio , " NÃO " );
i1 . setValue ( planoSugerido ," Ouro " );
correntistas . add ( i1 );
// Mais uma instância , só para exemplificar ...
Instance i2 = new Instance ( correntistas . numAttributes ());
i2 . setValue ( nome ," Ian Schreiber " );
i2 . setValue ( profissão ," Professor " );
i2 . setValue ( saldo ,3800.0);
i2 . setValue ( idade ,52);
i2 . setValue ( anosConta ,11);
i2 . setValue ( empréstimoPrévio , " SIM " );
i2 . setValue ( planoSugerido ," Platina " );
correntistas . add ( i2 );
// Podemos " imprimir " a relação .
System . out . println ( correntistas );
}
}
A relação criada com a classe
CreateArtificialARFF
Listagem 3: Relação criada pela classe
1
2
3
4
5
6
7
8
9
10
11
12
13
(listagem 2) é mostrada na listagem 3.
CreateArtificialARFF
(listagem 2).
@relation correntistas
@attribute
@attribute
@attribute
@attribute
@attribute
@attribute
@attribute
nome string
profissao { Professor , Bolsista , Motorista , Médico , Pesquisador }
saldo numeric
idade numeric
anosComoCorrentista numeric
emprestimoPrevio { SIM , NÃO }
planoSugerido { Prata , Ouro , Platina }
@data
' Sid Sackson ' , Médico ,4300 ,48 ,7 , NÃO , Ouro
' Ian Schreiber ' , Professor ,3800 ,52 ,11 , SIM , Platina
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
4
4.1
9
Weka Árvores de Decisão
Introdução
A API Weka implementa um classicador baseado em árvores de decisão (algoritmo C4.5 [7]) que pode classicar
dados com atributos numéricos e nominais (mas com classe sempre representada por atributo nominal). Uma
outra vantagem de classicadores baseados em árvores de decisão é que os modelos criados para classicação
podem ser facilmente interpretáveis.
4.2
Usando Árvores de Decisão em Aplicações em Java
Como um primeiro exemplo simples de uso de árvores de decisão em aplicações, veremos uma aplicação que cria
uma árvore de decisão a partir de dados lidos de um arquivo
.ARFF e classica estes mesmos dados com a árvore
criada, mostrando como resultado a árvore e a quantidade de instâncias classicadas corretamente.
Árvores de decisão baseadas no algoritmo C4.5 são implementadas na API Weka como instâncias da classe
J48.
Instâncias desta classe podem ser construídas e usadas com valores default para parâmetros criação de árvores
de decisão, e métodos
setXXX
podem ser usados para modicação destes parâmetros.
classifyInstance da instância de J48 que recebe como arguInstance e retorna o índice da classe para aquela instância. Em instâncias já rotuladas
Após o treinamento podemos usar o método
mento uma instância de
podemos recuperar a classe original para vericar a performance do classicador baseado em redes neurais.
A classe
ExemploJ48,
que é mostrada na listagem 4, implementa estes passos (leitura de instâncias, criação da
árvore, classicação dos próprios dados usados para treinamento).
Listagem 4: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
ExemploJ48,
que demonstra o uso de uma árvore de decisão em uma aplicação em Java.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import java . io . FileReader ;
import weka . classifiers . trees . J48 ;
import weka . core .*;
/* *
* Esta classe demonstra o uso de um classificador baseado em árvores de decisão . A classe
* lê um arquivo no formato . ARFF , cria o classificador e classifica o próprio arquivo original .
*/
public class ExemploJ48
{
public static void main ( String [] args ) throws Exception
{
// Usaremos a base de flores íris .
FileReader reader = new FileReader ("/ home / rafael / Java / APIs / Weka / data / iris . arff " );
Instances instâncias = new Instances ( reader );
instâncias . setClassIndex (4);
// Criamos o classificador baseado em árvores de decisão .
J48 árvore = new J48 ();
árvore . buildClassifier ( instâncias );
// Mostramos a árvore como texto .
System . out . println ( árvore );
// Mostramos a árvore como código .
System . out . println ( árvore . toSource ( " ClassificadorJ48 " ));
// Agora vamos classificar cada dado original com esta rede .
int corretas = 0;
for ( int i =0; i < instâncias . numInstances (); i ++)
{
// Recuperamos cada uma das instâncias .
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
10
37
38
39
40
41
42
43
44
45
46
47
48
Mineração e Visualização de Dados usando Java
Instance instância = instâncias . instance (i );
// Classificamos esta instância .
int classe = ( int )( árvore . classifyInstance ( instância ));
if ( classe == ( int ) instância . classValue ()) corretas ++;
}
// Relatório simples :
System . out . println ( " De "+ instâncias . numInstances ()+ " instâncias , "+ corretas + " ("+
(100.*( corretas /(1.0* instâncias . numInstances ())))+ " %) foram classificadas corretamente . " );
// De 150 instâncias , 147 (98.0%) foram classificadas corretamente .
}
}
ExemploJ48 (listagem 4) usamos o método System.out.println para
imprimir o conteúdo da instância da classe J48. O resultado da chamada deste método é mostrado na listagem 5.
Na linha 29 da aplicação na classe
Listagem 5: Conteúdo textual da árvore de decisão criada na classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ExemploJ48.
J48 pruned tree
-----------------petalwidth <= 0.6: Iris - setosa (50.0)
petalwidth > 0.6
|
petalwidth <= 1.7
|
|
petallength <= 4.9: Iris - versicolor (48.0/1.0)
|
|
petallength > 4.9
|
|
|
petalwidth <= 1.5: Iris - virginica (3.0)
|
|
|
petalwidth > 1.5: Iris - versicolor (3.0/1.0)
|
petalwidth > 1.7: Iris - virginica (46.0/1.0)
Number of Leaves
:
Size of the tree :
5
9
Na linha 31 da aplicação na classe
ExemploJ48 (listagem 4) executamos o método toSource da classe J48.
Este
método cria uma representação da árvore na forma de uma classe em Java (cujo nome é dado pelo argumento
para o método) que pode ser analisada e incorporada em projetos esta classe contém um método estático
(classify) que recebe como argumento um array de
A classe
ClassificadorJ48,
Objects
e retorna o índice da classe (como um double).
mostrada na listagem 6, mostra a classe criada pelo método
toSource,
na forma-
tação original.
Listagem 6: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ClassificadorJ48,
criada pela instância de
J48
na classe
ExemploJ48.
class ClassificadorJ48 {
public static double classify ( Object [] i)
throws Exception {
double p = Double . NaN ;
p = ClassificadorJ48 . N183f74d0 (i );
return p;
}
static double N183f74d0 ( Object [] i) {
double p = Double . NaN ;
if (i [3] == null ) {
p = 0;
} else if ((( Double ) i [3]). doubleValue () <= 0.6) {
p = 0;
} else if ((( Double ) i [3]). doubleValue () > 0.6) {
p = ClassificadorJ48 . Ne102dc1 (i );
}
return p;
}
static double Ne102dc1 ( Object [] i ) {
double p = Double . NaN ;
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
11
if (i [3] == null ) {
p = 1;
} else if ((( Double ) i [3]). doubleValue () <= 1.7) {
p = ClassificadorJ48 . N82c01f2 (i );
} else if ((( Double ) i [3]). doubleValue () > 1.7) {
p = 2;
}
return p;
}
}
static double N82c01f2 ( Object [] i ) {
double p = Double . NaN ;
if (i [2] == null ) {
p = 1;
} else if ((( Double ) i [2]). doubleValue ()
p = 1;
} else if ((( Double ) i [2]). doubleValue ()
p = ClassificadorJ48 . N1337963 (i );
}
return p;
}
static double N1337963 ( Object [] i ) {
double p = Double . NaN ;
if (i [3] == null ) {
p = 2;
} else if ((( Double ) i [3]). doubleValue ()
p = 2;
} else if ((( Double ) i [3]). doubleValue ()
p = 1;
}
return p;
}
<= 4.9) {
> 4.9) {
<= 1.5) {
> 1.5) {
Embora possamos armazenar a estrutura da árvore de decisão como código, isto pode não ser prático em muitos
casos pois requer adaptação e recompilação. Podemos armazenar a estrutura da árvore serializada para uso posterior, separando as funções de criação e aplicação da árvore. O armazenamento da árvore serializada é simples
pois a classe
J48
implementa a interface
Serializable,
podendo ser armazenada em disco com poucas linhas
de código.
Duas classes, mostradas a seguir, demonstram como armazenar uma árvore criada para uso posterior. A classe
ExemploJ48_2_Create,
lizada em um arquivo usando a classe
Listagem 7: A classe
.ARFF e
ObjectOutputStream.
na listagem 7, lê um arquivo
ExemploJ48_2_Create,
cria uma instância de
J48,
armazenando-a seria-
que demonstra o uso de uma árvore de decisão em uma aplicação
em Java.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import java . io .*;
import weka . classifiers . trees . J48 ;
import weka . core .*;
/* *
* Esta classe demonstra o uso de um classificador baseado em árvores de decisão . A classe
* lê um arquivo no formato . ARFF , cria o classificador e o armazena para uso posterior .
*/
public class ExemploJ48_2_Create
{
public static void main ( String [] args ) throws Exception
{
// Usaremos a base de flores wine .
FileReader reader = new FileReader ("/ home / rafael / Java / APIs / Weka / data / wine . arff " );
Instances instâncias = new Instances ( reader );
instâncias . setClassIndex (13);
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
12
25
26
27
28
29
30
31
32
33
34
Mineração e Visualização de Dados usando Java
// Criamos o classificador baseado em árvores de decisão .
J48 árvore = new J48 ();
árvore . buildClassifier ( instâncias );
// Serializamos a árvore . Outros mecanismos de serialização poderiam ser usados .
ObjectOutputStream oos = new ObjectOutputStream ( new FileOutputStream (" tree - wine . ser " ));
oos . writeObject ( árvore );
oos . close ();
}
}
ExemploJ48_2_Create (listagem 7) é recuperada e usada para classicar
ExemploJ48_2_Use (listagem 8).
A árvore criada e armazenada na classe
dados, como demonstrado na classe
Listagem 8: A classe
ExemploJ48_2_Use,
que demonstra o uso de uma árvore de decisão em uma aplicação em
Java.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import java . io .*;
import weka . classifiers . trees . J48 ;
import weka . core .*;
/* *
* Esta classe demonstra o uso de um classificador baseado em árvores de decisão . A classe
* lê um arquivo no formato . ARFF , cria o classificador e classifica o próprio arquivo original .
*/
public class ExemploJ48_2_Use
{
public static void main ( String [] args ) throws Exception
{
// Recuperamos a árvore previamente serializada .
ObjectInputStream ois = new ObjectInputStream ( new FileInputStream (" tree - wine . ser " ));
J48 árvore = ( J48 ) ois . readObject ();
ois . close ();
// Usaremos a base de flores íris .
FileReader reader = new FileReader ("/ home / rafael / Java / APIs / Weka / data / wine . arff " );
Instances instâncias = new Instances ( reader );
instâncias . setClassIndex (13);
// Agora vamos classificar cada dado original com esta rede .
int corretas = 0;
for ( int i =0; i < instâncias . numInstances (); i ++)
{
// Recuperamos cada uma das instâncias .
Instance instância = instâncias . instance (i );
// Classificamos esta instância .
int classe = ( int )( árvore . classifyInstance ( instância ));
if ( classe == ( int ) instância . classValue ()) corretas ++;
}
// Relatório simples :
System . out . println ( " De "+ instâncias . numInstances ()+ " instâncias , "+ corretas + " ("+
(100.*( corretas /(1.0* instâncias . numInstances ())))+ " %) foram classificadas corretamente . " );
// De 178 instâncias , 176 (98.87640449438202%) foram classificadas corretamente .
}
}
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
5
5.1
Weka Redes Neurais
13
Multilayer Perceptron
Introdução
A API Weka implementa um classicador baseado em redes neurais de perceptrons em múltiplas camadas (Multilayer Perceptron) que são capazes de classicar dados cujos atributos sejam numéricos (com classe discreta).
Este classicador apresenta excelente capacidade de separação de dados, mas pela sua natureza seu algoritmo de
treinamento que pode ser computacionalmente custoso. Uma apresentação dos detalhes do algoritmo e de sua
fundamentação matemática estão fora do escopo deste documento as referências [2, 4, 6, 9, 11] apresentam
mais detalhes do algoritmo, suas variantes e aplicações.
5.2
Usando Redes Neurais Multilayer Perceptron em Aplicações em Java
Como um primeiro exemplo simples de uso de redes neurais (Multilayer Perceptron) em aplicações, veremos
uma aplicação que cria uma rede neural, a treina usando dados lidos de um arquivo
.ARFF e classica estes mes-
mos dados com a rede treinada, mostrando como resultado a quantidade de instâncias classicadas corretamente.
MultilayerPerceptron,
A classe
parte da API Weka, é usada para representar e processar uma rede neural
Multilayer Perceptron. Para usar uma instância de
MultilayerPerceptron devemos primeiro ter uma instância
Instances devidamente populada e com uma indicação de qual atributo deve ser usado como classe. A insMultilayerPerceptron é criada e usamos vários métodos setXXX para congurar a sua arquitetura
e parâmetros de criação e treinamento. Finalmente usamos a instância de Instances como argumento para o
método buildClassifier da instância da rede para treinar a mesma.
de
tância de
Após o treinamento podemos usar o método
instância de
Instance
classifyInstance
da rede que recebe como argumento uma
e retorna o índice da classe para aquela instância. Em instâncias já rotuladas podemos
recuperar a classe original para vericar a performance do classicador baseado em redes neurais.
A classe
ExemploNN,
que é mostrada na listagem 9, implementa estes passos (leitura de instâncias, criação da
rede, treinamento e classicação dos próprios dados usados para treinamento).
Listagem 9:
A classe
ExemploNN,
que demonstra o uso de uma rede neural Multilayer Perceptron em uma
aplicação em Java.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import java . io . FileReader ;
import weka . classifiers . functions . MultilayerPerceptron ;
import weka . core .*;
/* *
* Esta classe demonstra o uso de um classificador baseado em multilayer perceptrons . A classe
* lê um arquivo no formato . ARFF , cria o classificador e classifica o próprio arquivo original .
*/
public class ExemploNN
{
public static void main ( String [] args ) throws Exception
{
// Usaremos a base de flores íris .
FileReader reader = new FileReader ("/ home / rafael / Java / APIs / Weka / data / iris . arff " );
Instances instâncias = new Instances ( reader );
instâncias . setClassIndex (4);
// Criamos o classificador baseado em Multilayer Perceptrons
MultilayerPerceptron mlp = new MultilayerPerceptron ();
mlp . setAutoBuild ( true );
mlp . setLearningRate (0.3);
mlp . setMomentum (0.2);
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
14
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Mineração e Visualização de Dados usando Java
mlp . setTrainingTime (1000);
mlp . setHiddenLayers ("5" );
// Treinamos a rede .
mlp . buildClassifier ( instâncias );
// Agora vamos classificar cada dado original com esta rede .
int corretas = 0;
for ( int i =0; i < instâncias . numInstances (); i ++)
{
// Recuperamos cada uma das instâncias .
Instance instância = instâncias . instance (i );
// Classificamos esta instância .
int classe = ( int )( mlp . classifyInstance ( instância ));
if ( classe == ( int ) instância . classValue ()) corretas ++;
}
// Relatório simples :
System . out . println ( " De "+ instâncias . numInstances ()+ " instâncias , "+ corretas + " ("+
(100.*( corretas /(1.0* instâncias . numInstances ())))+ " %) foram classificadas corretamente . " );
// De 150 instâncias , 148 (98.66666666666667%) foram classificadas corretamente .
}
}
Eventualmente será mais interessante separar o treinamento da rede neural da classicação de dados com ela assim podemos treinar a rede neural com um conjunto adequado de dados, armazenar a rede treinada e usá-la em
outros momentos para a classicação. A separação da fase de treinamento da fase de classicação é trivial: como
muitas classes da API Weka implementam a interface
Serializable, podemos armazenar instâncias inteiras em
objetos em arquivos com poucas linhas de código.
ExemploNN2_Train, na listagem 10,
MultilayerPerceptron, treinando-a e armazenando-a em um
usando a classe ObjectOutputStream.
Esta técnica é implementada em duas classes, mostradas a seguir. A classe
lê um arquivo
.ARFF
e cria uma instância de
arquivo em disco através de serialização
Listagem 10: A classe
ExemploNN2_Train,
que cria e treina uma rede neural Multilayer Perceptron para uso
posterior.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import java . io .*;
import weka . classifiers . functions . MultilayerPerceptron ;
import weka . core .*;
/* *
* Esta classe demonstra o uso de um classificador baseado em multilayer perceptrons . A classe
* lê um arquivo no formato . ARFF , cria o classificador e o armazena para uso posterior .
*/
public class ExemploNN2_Train
{
public static void main ( String [] args ) throws Exception
{
// Usaremos a base de segmentos de imagens digitais .
FileReader reader = new FileReader ("/ home / rafael / Java / APIs / Weka / data / segment - challenge . arff " );
Instances instâncias = new Instances ( reader );
instâncias . setClassIndex (19);
// Criamos o classificador baseado em Multilayer Perceptrons
MultilayerPerceptron mlp = new MultilayerPerceptron ();
mlp . setAutoBuild ( true );
mlp . setLearningRate (0.5);
mlp . setMomentum (0.2);
mlp . setTrainingTime (2000);
mlp . setHiddenLayers (" 10 " );
// Treinamos a rede .
mlp . buildClassifier ( instâncias );
// Serializamos a rede treinada . Outros mecanismos de serialização poderiam ser usados .
ObjectOutputStream oos = new ObjectOutputStream ( new FileOutputStream ("nn - segment . ser " ));
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
36
37
38
39
40
15
oos . writeObject ( mlp );
oos . close ();
}
}
A classe
ExemploNN2_Train, mostrada na listagem 11, recupera a instância de MultilayerPerceptron treinada
ExemploNN2_Train usando uma instância de ObjectInputStream, e usa a rede para classicar uma
na classe
base compatível de dados.
Listagem 11: A classe
ExemploNN2_Train,
que usa a rede treinada anteriormente para classicar um conjunto
de dados.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import java . io .*;
import weka . classifiers . functions . MultilayerPerceptron ;
import weka . core .*;
/* *
* Esta classe demonstra o uso de um classificador baseado em multilayer perceptrons . A classe
* lê uma instância de MultilayerPerceptron previamente criada e serializada e a usa para
* classificar dados compatíveis obtidos de um arquivo no formato . ARFF .
*/
public class ExemploNN2_Use
{
public static void main ( String [] args ) throws Exception
{
// Lemos a instância de MultilayerPerceptron previamente serializada .
ObjectInputStream ois = new ObjectInputStream ( new FileInputStream ("nn - segment . ser " ));
MultilayerPerceptron mlp = ( MultilayerPerceptron ) ois . readObject ();
ois . close ();
// Usaremos outra base de segmentos de imagens digitais para classificação .
FileReader reader = new FileReader ("/ home / rafael / Java / APIs / Weka / data / segment - test . arff " );
Instances instâncias = new Instances ( reader );
instâncias . setClassIndex (19);
// Agora vamos classificar cada dado com esta rede .
int corretas = 0;
for ( int i =0; i < instâncias . numInstances (); i ++)
{
// Recuperamos cada uma das instâncias .
Instance instância = instâncias . instance (i );
// Classificamos esta instância .
int classe = ( int )( mlp . classifyInstance ( instância ));
if ( classe == ( int ) instância . classValue ()) corretas ++;
}
// Relatório simples :
System . out . println ( " De "+ instâncias . numInstances ()+ " instâncias , "+ corretas + " ("+
(100.*( corretas /(1.0* instâncias . numInstances ())))+ " %) foram classificadas corretamente . " );
// De 810 instâncias , 767 (94.69135802469137%) foram classificadas corretamente .
}
}
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
16
6
Mineração e Visualização de Dados usando Java
Weka Agrupamento com
6.1
K-Means
Introdução
A API Weka implementa também algoritmos de agrupamento; entre eles o K-Means. Este algoritmo possibilita
a criação de grupos a partir de instâncias com atributos numéricos. Instâncias semelhantes entre si no espaço de
atributos serão incluidas no mesmo grupo; instâncias em outro grupo serão, por denição, distantes no espaço
de atributos. Mais explicações sobre este algoritmo podem ser vistas em [6, 9, 11].
6.2
Usando o Algoritmo K-Means em Aplicações em Java
A classe
SimpleKMeans, parte da API Weka, é usada para representar e fazer o agrupamento de dados numéricos.
Para fazer o agrupamento devemos criar uma instância da classe, indicar o número de grupos a ser criado com
setNumClusters, informar outros parâmetros se necessário e executar o agrupamento com
buildClusterer que recebe como argumento uma instância de Instances com a base de dados.
o método
o método
getClusterSizes
getClusterCentroids. Outros métodos perExemploKMeans, mostrada na listagem 12, mostra
Ao nal do agrupamento podemos obter o número de dados em cada grupo com uma chamada a
e obter as médias (centróides) dos grupos com uma chamada a
mitem obter outras estatísticas sobre agrupamento. A classe
como executar estes passos.
Listagem 12: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
ExemploKMeans, que demonstra o uso do algoritmo K-Means em uma aplicação em Java.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package weka ;
import java . io . FileReader ;
import weka . clusterers . SimpleKMeans ;
import weka . core .*;
/* *
* Esta classe demonstra o uso de um algoritmo de agrupamento (K - Means ). A classe
* lê um arquivo no formato . ARFF , cria o agrupamento e classifica o próprio arquivo original .
*/
public class ExemploKMeans
{
public static void main ( String [] args ) throws Exception
{
// Usaremos a base de flores íris .
FileReader reader = new FileReader ("/ home / rafael / Java / APIs / Weka / data / iris . arff " );
Instances instâncias = new Instances ( reader );
// Queremos ignorar a classe das instâncias .
instâncias . deleteAttributeAt (4);
// Criamos o agrupamento .
SimpleKMeans agrupamento = new SimpleKMeans ();
agrupamento . setNumClusters (3);
agrupamento . setDisplayStdDevs ( true );
agrupamento . buildClusterer ( instâncias );
// Mostramos estatísticas sobre o agrupamento .
Instances clusterCenters = agrupamento . getClusterCentroids ();
Instances clusterSTDDEVs = agrupamento . getClusterStandardDevs ();
int [] clusterSizes = agrupamento . getClusterSizes ();
for ( int cluster =0; cluster < clusterCenters . numInstances (); cluster ++)
{
System . out . println ( " Cluster # " +( cluster +1)+ ": "+ clusterSizes [ cluster ]+ " dados ." );
System . out . println ( "
Centróide : "+ clusterCenters . instance ( cluster ));
System . out . println ( "
STDDEV : " + clusterSTDDEVs . instance ( cluster ));
}
}
}
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
O resultado da execução da classe
Listagem 13: A classe
ExemploKMeans
ExemploKMeans,
17
é mostrado na listagem 13.
que demonstra o uso de uma rede neural Multilayer Perceptron em uma
aplicação em Java.
1
2
3
4
5
6
7
8
9
Cluster #1: 61 dados .
Centróide : 5.888525 ,2.737705 ,4.396721 ,1.418033
STDDEV : 0.448738 ,0.293351 ,0.52693 ,0.272341
Cluster #2: 50 dados .
Centróide : 5.006 ,3.418 ,1.464 ,0.244
STDDEV : 0.35249 ,0.381024 ,0.173511 ,0.10721
Cluster #3: 39 dados .
Centróide : 6.846154 ,3.082051 ,5.702564 ,2.079487
STDDEV : 0.502544 ,0.279917 ,0.519355 ,0.281144
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
18
7
7.1
Mineração e Visualização de Dados usando Java
Visualização usando Coordenadas Paralelas
Introdução
A técnica Coordenadas Paralelas (Parallel Coordinates) [5] permite a visualização de dados multidimensionais
de qualquer natureza (numéricos ou nominais) em um gráco em duas dimensões.
As dimensões dos dados
são transformadas em eixos paralelos, tradicionalmente apresentados na vertical. Cada dado multidimensional
é plotado como uma linha que passa pelos eixos paralelos, cruzando estes na altura dos valores dos atributos
correspondentes.
Este tipo de gráco permite a visualização de agrupamentos como feixes de dados e outliers como linhas fora
destes feixes. Permite também a visualização da distribuição dos valores ao longo de uma dimensão/eixo e da
correlação dos valores entre dimensões/eixos, assim como estimativa da separabilidade entre classes em função
dos valores dos atributos. O gráco pode ser usado com dados rotulados ou não, numéricos (em qualquer escala,
que pode ser diferente para dimensões diferentes) ou nominais. Uma limitação da técnica, pela sua própria natureza, é que os eixos são mostrados paralelos uns aos outros, e correlações entre valores de dimensões diferentes
podem ou não aparecer em evidência dependendo de como os eixos são arranjados por exemplo, se existir
correlação entre duas dimensões mas os eixos correspondentes estiverem distantes no gráco, a correlação pode
não aparecer em evidência.
A visualização de dados com coordenadas paralelas é mais facilmente compreendida com um exemplo. A listagem 14 mostra um arquivo
.ARFF
com uma versão reduzida a relação de dados do problema jogar dependendo
do tempo (veja também a listagem 1). Esta relação contém somente três instâncias, com cinco atributos, dos
quais o último é considerado a classe ou consequente.
A gura 5 mostra o gráco de coordenadas paralelas
correspondente a estes dados nesta gura podemos ver os valores dos atributos para cada instância plotadas
da forma descrita.
Listagem 14: O arquivo
weather-3.arff,
com uma versão reduzida da relação do problema jogar dependendo
do tempo.
1
2
3
4
5
6
7
8
9
10
11
12
@relation weather -3
@attribute
@attribute
@attribute
@attribute
@attribute
outlook { sunny , overcast , rainy }
temperature real
humidity real
windy { TRUE , FALSE }
play { yes , no }
@data
sunny ,85 ,85 , FALSE , no
overcast ,83 ,86 , FALSE , yes
rainy ,65 ,70 , TRUE , no
Figura 5: Plotagem das coordenadas paralelas da relação
http://www.lac.inpe.br/∼rafael.santos
weather-3.arff.
Rafael Santos
Mineração e Visualização de Dados usando Java
7.2
19
Implementação
O gráco das coordenadas paralelas é implementado como um componente gráco em Java. Este componente
pode ser usado em aplicações grácas para visualização e para criação de grácos em arquivo. O componente
Instances, da API Weka, para acessar todos os atributos e valores de uma base de dados
Instance contém todos os dados apresentados em um arquivo .ARFF, menos os comentários do
usará uma instância de
(instâncias de
mesmo).
O componente permitirá a conguração de sua aparência (cores, margens, fontes, etc.) através de uma classe
externa. A classe que implementa o componente é chamada
ParallelCoordinatesComponent
e é mostrada
na listagem 15.
Listagem 15: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
ParallelCoordinatesComponent.
/* *
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package parcoords ;
import java . awt .*;
import java . util .*;
import javax . swing . JComponent ;
import weka . core .*;
public class ParallelCoordinatesComponent extends JComponent
{
// Cópia local das instâncias .
private Instances instances ;
// Valores extremos para atributos numéricos .
private TreeMap < Integer , double [] > extrema ;
// Valores extremos globais para atributos numéricos .
private double [] globalExtrema ;
// Valores discretos para atributos nominais .
private TreeMap < Integer , HashSet < String > > nominalValues ;
// Devemos usar valores extremos de forma global ?
private boolean useGlobalExtrema ;
// Devemos usar cores diferentes para as classes ( último atributo )?
private boolean useDifferentColors ;
// Quanto podemos " sacudir " as linhas para evitar superposição ?
private double wiggle ;
// Criamos uma isntância local para as constantes do gráfico .
private ParallelCoordinatesConstants consts = new ParallelCoordinatesConstants ();
/* *
* O construtor para a classe , que inicializa os atributos e cria a interface do componente .
*/
public ParallelCoordinatesComponent ( Instances instances ,
boolean useGlobalExtrema , boolean useDifferentColors ,
double wiggle )
{
super ();
// Copia atributos .
this . instances = instances ;
this . useGlobalExtrema = useGlobalExtrema ;
this . useDifferentColors = useDifferentColors ;
this . wiggle = wiggle ;
// Calcula estatísticas básicas sobre os dados .
int nAxis = instances . numAttributes ();
// Alocamos as estruturas - base .
extrema = new TreeMap < Integer , double [] >();
nominalValues = new TreeMap < Integer , HashSet < String > >();
globalExtrema = new double [2];
// Alocamos os elementos necessários nos mapas ( dependendo do tipo do atributo ).
for ( int a =0; a < nAxis ;a ++)
{
Attribute attrib = instances . attribute (a );
if ( attrib . isNumeric ())
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
20
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
Mineração e Visualização de Dados usando Java
extrema . put (a , new double []{ Double . MAX_VALUE , Double . MIN_VALUE });
else if ( attrib . isNominal ())
nominalValues . put (a , new HashSet < String >());
}
// Para cada instância : se numérica atualizamos os extremos , se discreta incluimos os valores .
for ( int i =0; i < instances . numInstances (); i ++)
{
// Recuperamos uma instância ... a API do Weka permite escrever código bizarro !
Instance instance = instances . instance (i );
// Para cada atributo desta instância ...
for ( int a =0; a < nAxis ;a ++)
{
// Se for numérico ...
if ( instances . attribute (a ). isNumeric ())
{
// Recuperamos o valor e mudamos os extremos para aquele atributo e
// os extremos globais .
double value = instance . value (a );
extrema . get (a )[0] = Math . min ( extrema . get (a )[0] , value );
extrema . get (a )[1] = Math . max ( extrema . get (a )[1] , value );
globalExtrema [0] = Math . min ( globalExtrema [0] , value );
globalExtrema [1] = Math . max ( globalExtrema [1] , value );
}
// Se for nominal ...
else if ( instances . attribute (a ). isNominal ())
{
// Recuperamos todos os valores para aquele atributo nominal .
Enumeration valuesE = instances . attribute (a ). enumerateValues ();
// Armazenamos na nossa estrutura .
while ( valuesE . hasMoreElements ())
nominalValues . get (a ). add (( String ) valuesE . nextElement ());
}
} // Fim do laço para cada atributo ...
} // Fim do laço para cada instância ...
}
/* *
* Este método muda os valores extremos globais .
*/
public void setExtrema ( double min , double max )
{
globalExtrema [0] = min ;
globalExtrema [1] = max ;
}
/* *
* Este método cria o componente , chamando vários métodos auxiliares .
*/
protected void paintComponent ( Graphics g)
{
// Recuperamos as dimensões do componente .
int width = getSize (). width ;
int height = getSize (). height ;
// Recuperamos a área de plotagem ( dimensões menos as bordas ).
int plotWidth = width - consts . leftMargin - consts . rightMargin ;
int plotHeight = height - consts . topMargin - consts . bottomMargin ;
// Usaremos Graphics2D para melhor aparência .
Graphics2D g2d = ( Graphics2D )g ;
g2d . setRenderingHint ( RenderingHints . KEY_ANTIALIASING ,
RenderingHints . VALUE_ANTIALIAS_ON );
// Pintamos o fundo do componente .
g2d . setColor ( consts . plottingAreaBackground );
g2d . fillRect (0 ,0 , width , height );
// Pintamos o fundo da área de plotagem .
g2d . setColor ( consts . plottingAreaColor );
g2d . fillRect ( consts . leftMargin , consts . topMargin , plotWidth , plotHeight );
// Pintamos primeiro os dados ( para que os eixos não desapareçam sob eles !).
// Para cada instância ...
for ( int inst =0; inst < instances . numInstances (); inst ++)
{
Instance instance = instances . instance ( inst );
// Calculamos as coordenadas " de " e " para " cada segmento .
double x =0 , y =0 , lastX =0 , lastY =0;
// Para cada um de seus atributos ...
for ( int attr =0; attr < instance . numAttributes (); attr ++)
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
21
{
// Recuperamos as coordenadas X e Y de cada atributo ( de forma diferente para
// atributos numéricos e nominais ).
switch ( instances . attribute ( attr ). type ())
{
case Attribute . NUMERIC :
{
x = getXForAttribute ( attr , plotWidth );
y = getYForNumericAttribute ( instance . value ( attr ), attr , plotHeight );
break ;
}
case Attribute . NOMINAL :
{
x = getXForAttribute ( attr , plotWidth );
// Qual é o nome do atributo para recuperar a coordenada Y?
String sAttr = instances . instance ( inst ). stringValue ( attr );
y = getYForNominalAttribute ( sAttr , attr , plotHeight );
break ;
}
}
// Devemos sacudir a linha para evitar superposição ? Cada unidade de sacudida
// causa uma variação entre -50 e 50 pixels ).
if ( wiggle > 0)
{
double wpix = ( wiggle *50) -( wiggle *100* Math . random ());
y += wpix ;
}
// Somente plotaremos quando tivermos dois pares de X , Y.
if ( attr > 0)
{
// Usamos uma só cor ou uma para cada classe ?
if ( useDifferentColors )
{
// Assumimos que o último atributo é a classe , e a cor será selecionada
// a partir do valor deste .
int colorIndex = ( int ) instance . value ( instance . numAttributes () -1);
g2d . setColor ( consts . linesColors [ colorIndex ]);
}
else g2d . setColor ( consts . plottingLineColor );
// Mudamos o stroke e desenhamos a linha .
g2d . setStroke ( consts . stroke );
g2d . drawLine (( int )x ,( int )y ,( int ) lastX ,( int ) lastY );
}
// Guardamos as últimas coordenadas usadas para o próximo segmento .
lastX = x ; lastY = y;
} // Fim do laço para cada atributo .
} // Fim do laço para cada instância .
// Voltamos ao stroke normal .
g2d . setStroke ( new BasicStroke (1 f ));
// OK , os dados foram plotados , vamos plotar os eixos paralelos , novamente com métodos
// diferentes para atributos numéricos e nominais .
for ( int attr =0; attr < instances . numAttributes (); attr ++)
{
switch ( instances . attribute ( attr ). type ())
{
case Attribute . NUMERIC : paintNumericAxis ( g2d , attr , plotWidth , plotHeight ); break ;
case Attribute . NOMINAL : paintNominalAxis ( g2d , attr , plotWidth , plotHeight ); break ;
}
}
// Pintamos o título dos atributos .
g2d . setFont ( consts . axisTitlesFont );
for ( int attr =0; attr < instances . numAttributes (); attr ++)
{
String name = instances . attribute ( attr ). name ();
g2d . drawString ( name ,( int ) getXForAttribute ( attr , plotWidth ), consts . topMargin -20);
}
// Pintamos o título do gráfico , que será o da relação no arquivo . ARFF .
g2d . setFont ( consts . titleFont );
FontMetrics m = g2d . getFontMetrics ();
String name = instances . relationName ();
int x = width /2 - m. stringWidth ( name )/2;
g2d . drawString ( name ,x , consts . topMargin -45);
}
/*
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
22
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
Mineração e Visualização de Dados usando Java
* Calculamos a coordenada X para um atributo específico , considerando a área de plotagem .
* O valor da coordenada X depende somente do índice do eixo do atributo correspondente .
*/
private double getXForAttribute ( int axis , int pwidth )
{
int nAxis = instances . numAttributes ();
double x = consts . leftMargin + axis *( pwidth /( nAxis -1));
return x;
}
/*
* Calculamos a coordenada Y para um valor e atributo numérico específico , considerando a
* área de plotagem . O valor será normalizado dependendo dos valores extremos globais ou do
* atributo .
*/
private double getYForNumericAttribute ( double value , int axis , int pheight )
{
double delta , val =0;
// Usamos extremos globais ?
if ( useGlobalExtrema )
{
int nAxis = instances . numAttributes ();
delta = globalExtrema [1] - globalExtrema [0];
val = consts . topMargin + pheight *(1 -( value - globalExtrema [0])/ delta );
}
else
{
delta = extrema . get ( axis )[1] - extrema . get ( axis )[0];
val = consts . topMargin + pheight *(1 -( value - extrema . get ( axis )[0])/ delta );
}
return val ;
}
/*
* Calculamos a coordenada Y para um valor e atributo nominal específico , considerando a
* área de plotagem .
*/
private double getYForNominalAttribute ( String whichAttr , int axis , int pheight )
{
// Muito mais fácil com nominais do que com numéricos !
ArrayList < String > theseValues = new ArrayList < String >( nominalValues . get ( axis ));
int index = theseValues . indexOf ( whichAttr );
double y = consts . topMargin + index *( pheight /( nominalValues . get ( axis ). size () -1));
return y;
}
/*
* Desenhamos um eixo para atributo numérico .
*/
private void paintNumericAxis ( Graphics2D g2d , int axis , int pwidth , int pheight )
{
int nAxis = instances . numAttributes ();
// Calculamos o valor para espaçamento das marcas no eixo .
double delta ;
if ( useGlobalExtrema ) delta = ( globalExtrema [1] - globalExtrema [0])/( consts . numMarks -1);
else delta = ( extrema . get ( axis )[1] - extrema . get ( axis )[0])/( consts . numMarks -1);
// Desenhamos o eixo .
g2d . setColor ( consts . plottingAxisColorForeground );
g2d . drawLine ( consts . leftMargin + axis *( pwidth /( nAxis -1)) , consts . topMargin ,
consts . leftMargin + axis *( pwidth /( nAxis -1)) , pheight + consts . topMargin );
// Desenhamos as marcas e rótulos .
g2d . setFont ( consts . axisLabelsFont );
for ( int n =0; n < consts . numMarks ;n ++)
{
// Marcas .
double tic = consts . topMargin +n *( pheight /( consts . numMarks -1));
g2d . drawLine ( consts . leftMargin + axis *( pwidth /( nAxis -1)) -5 ,( int ) tic ,
consts . leftMargin + axis *( pwidth /( nAxis -1))+5 ,( int ) tic );
// Rótulos .
double value ;
if ( useGlobalExtrema ) value = globalExtrema [1] -( n* delta );
else value = extrema . get ( axis )[1] -( n* delta );
g2d . setColor ( consts . plottingAxisColorBackground );
g2d . drawString ( String . format (" %5.2 f" , value ),
consts . leftMargin + axis *( pwidth /( nAxis -1))+6 ,( int ) tic +1);
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
}
23
g2d . setColor ( consts . plottingAxisColorForeground );
g2d . drawString ( String . format (" %5.2 f" , value ),
consts . leftMargin + axis *( pwidth /( nAxis -1))+5 ,( int ) tic );
}
/*
* Desenhamos um eixo para atributo nominal .
*/
private void paintNominalAxis ( Graphics2D g2d , int axis , int pwidth , int pheight )
{
// Internamente representamos os valores nominais deste eixo como um ArrayList -- é mais fácil .
ArrayList < String > theseValues = new ArrayList < String >( nominalValues . get ( axis ));
int nAxis = instances . numAttributes ();
// Desenhamos o eixo .
g2d . setColor ( consts . plottingAxisColorForeground );
g2d . drawLine ( consts . leftMargin + axis *( pwidth /( nAxis -1)) , consts . topMargin ,
consts . leftMargin + axis *( pwidth /( nAxis -1)) , pheight + consts . topMargin );
// Desenhamos as marcas e rótulos .
g2d . setFont ( consts . axisLabelsFont );
for ( int n =0; n < theseValues . size (); n ++)
{
// Marcas .
double tic = consts . topMargin +n *( pheight /( nominalValues . get ( axis ). size () -1));
g2d . drawLine ( consts . leftMargin + axis *( pwidth /( nAxis -1)) -5 ,( int ) tic ,
consts . leftMargin + axis *( pwidth /( nAxis -1))+5 ,( int ) tic );
// Rótulos .
g2d . setColor ( consts . plottingAxisColorBackground );
g2d . drawString ( String . format (" % -20 s" , theseValues . get (n )) ,
consts . leftMargin + axis *( pwidth /( nAxis -1))+6 ,( int ) tic +1);
g2d . setColor ( consts . plottingAxisColorForeground );
g2d . drawString ( String . format (" % -20 s" , theseValues . get (n )) ,
consts . leftMargin + axis *( pwidth /( nAxis -1))+5 ,( int ) tic );
}
}
/*
* Este método permite mudar o conjunto de constantes para a plotagem .
*/
public void setConsts ( ParallelCoordinatesConstants consts )
{
this . consts = consts ;
}
}
Algumas características interessantes implementadas na classe
ParallelCoordinatesComponent (e informadas
através de parâmetros passados para seu construtor) são:
.
Eixos de atributos numéricos são plotados considerando os valores máximo e mínimo do eixo (atributo).
Eixos numéricos podem ser normalizados para que indiquem valores entre o máximo e mínimo globais de
todos os atributos numéricos.
.
É possível usar uma única cor para todas as linhas que representam dados ou cores individuais para cada
classe ou categoria de cada linha. No caso de bases com rótulos para cada instância, assumimos que o
último atributo corresponde a classe, assim se um gráco com cores distintas for criado, cada classe terá
uma cor diferente (dentro de um limite de cores denidas, veja a classe
ParallelCoordinatesConstants
a seguir). No caso de bases sem rótulos ou classes podemos usar uma cor geral (cinza por default).
.
Em alguns casos podemos ter várias linhas superpostas no gráco isto pode ocorrer quando muitas
instâncias ocuparem o mesmo ponto no espaço de atributos ou quando existirem poucos valores discretos
para um atributo. Nestes casos podemos especiar um parâmetro chamado wiggle que dene uma pequena
perturbação aleatória na posição das coordenadas
Y
dos dados, justamente para evita que sejam plotados
uns sobre os outros (com os mais recentemente plotados ocultando os plotados anteriormente).
ParallelCoordinatesComponent (listagem 15) usa vários atributos da classe ParallelCoordinatesConstants, que representa um conjunto básico (modicável) de atributos que controlam a aparência do gráco.
A classe ParallelCoordinatesConstants é mostrada na listagem 16.
A classe
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
24
Mineração e Visualização de Dados usando Java
Listagem 16: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
ParallelCoordinatesConstants.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package parcoords ;
import java . awt .*;
/* *
* Esta classe contém campos para uso pelo componente ParallelCoordinatesConstants
* que definem aspectos do gráfico que são independentes dos dados em si .
* Em uma implementação mais completa deveríamos ter getters e setters e os campos
* deveriam ser declarados como privados .
*/
public class ParallelCoordinatesConstants
{
// Margens .
public int topMargin = 75;
public int bottomMargin = 15;
public int leftMargin = 15;
public int rightMargin = 200;
// Número de marcas nos eixos das coordenadas .
public int numMarks = 10;
// Cores para o gráfico ( comuns ).
public Color plottingAreaBackground = Color . WHITE ;
public Color plottingAreaColor = new Color (252 ,252 ,255);
public Color plottingAxisColorForeground = Color . BLACK ;
public Color plottingAxisColorBackground = Color . LIGHT_GRAY ;
public Color plottingLineColor = new Color (100 ,100 ,100 ,80);
// Tabela de cores para as linhas . Se o conjunto de dados tiver mais de 20
// cores pode ser necessário modificar esta tabela .
private int lineTransparency = 100;
public Color [] linesColors =
{
new Color (255 ,0 ,0 , lineTransparency ),
// Red
new Color (0 ,255 ,0 , lineTransparency ),
// Green
new Color (0 ,0 ,255 , lineTransparency ),
// Blue
new Color (255 ,255 ,0 , lineTransparency ),
// Yellow
new Color (0 ,255 ,255 , lineTransparency ),
// Cyan
new Color (255 ,0 ,255 , lineTransparency ),
// Magenta
new Color (140 ,140 ,140 , lineTransparency ), // Medium gray
new Color (60 ,60 ,60 , lineTransparency ),
// Dark gray
new Color (255 ,127 ,0 , lineTransparency ),
// Rg .
new Color (255 ,0 ,127 , lineTransparency ),
// R .B
new Color (0 ,127 ,255 , lineTransparency ),
// . gB
new Color (0 ,255 ,127 , lineTransparency ),
// . Gb
new Color (127 ,255 ,0 , lineTransparency ),
// rG .
new Color (127 ,0 ,255 , lineTransparency ),
// r .B
new Color (127 ,0 ,0 , lineTransparency ),
// Dark red
new Color (0 ,127 ,0 , lineTransparency ),
// Dark green
new Color (0 ,0 ,127 , lineTransparency ),
// Dark blue
new Color (127 ,127 ,0 , lineTransparency ),
// Dark yellow
new Color (0 ,127 ,127 , lineTransparency ),
// Dark cyan
new Color (127 ,0 ,127 , lineTransparency ),
// Dark magenta
};
// Tipo de linha ( por enquanto global ).
public Stroke stroke = new BasicStroke (3 f );
// Fontes para o gráfico .
public Font titleFont = new Font (" SansSerif " , Font . ITALIC ,24);
public Font axisTitlesFont = new Font (" SansSerif " , Font . BOLD ,18);
public Font axisLabelsFont = new Font (" Monospaced " , Font . BOLD ,16);
}
Com estas duas classes (e a API Weka) podemos criar aplicações simples para visualização de dados no formato
.ARFF
usando coordenadas paralelas. Uma primeira aplicação, que usa o componente praticamente sem
modicações para visualizar a base
iris.arff,
http://www.lac.inpe.br/∼rafael.santos
é mostrada na classe
ExemploPC1,
na listagem 17.
Rafael Santos
Mineração e Visualização de Dados usando Java
Listagem 17:
A classe
ExemploPC1,
25
que implementa um exemplo simples de visualização com coordenadas
paralelas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package parcoords ;
import java . awt . Dimension ;
import java . io .*;
import javax . swing . JFrame ;
import weka . core . Instances ;
/* *
* Exemplo simples de visualização com coordenadas paralelas .
*/
public class ExemploPC1
{
public static void main ( String [] args ) throws IOException
{
// Usaremos a base iris . arff .
FileReader reader =
new FileReader (" / home / rafael / Java / APIs / Weka / data / iris . arff " );
Instances instances = new Instances ( reader );
// Criamos o componente com as instâncias , sem normalização global , usando cores diferentes
// e sem " wiggle ".
ParallelCoordinatesComponent component =
new ParallelCoordinatesComponent ( instances , false , true ,0);
component . setPreferredSize ( new Dimension (1024 ,500));
// Montamos a UI para visualizar o componente .
JFrame frame = new JFrame ( " Parallel Coordinates " );
frame . getContentPane (). add ( component );
frame . pack ();
frame . setVisible ( true );
frame . setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE );
}
}
A gura 6 mostra o resultado (cópia da tela) da aplicação implementada na classe
Figura 6: Tela da aplicação da classe
Uma variante da visualização da base de dados
iris.arff
ExemploPC1
ExemploPC1
(listagem 17).
(listagem 17).
é implementada pela classe
ExemploPC2,
na lista-
gem 18. Esta variante usa normalização global nos eixos numéricos e um pequeno valor de wiggle para visualizar
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
26
Mineração e Visualização de Dados usando Java
melhor feixes de dados que são concentrados em pontos do eixo do atributo petalwidth, por exemplo. A gura 7
mostra o resultado (cópia da tela) da aplicação implementada na classe
Listagem 18: A classe
ExemploPC2,
ExemploPC2
(listagem 18).
que implementa outro exemplo simples de visualização com coordenadas
paralelas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package parcoords ;
import java . awt . Dimension ;
import java . io .*;
import javax . swing . JFrame ;
import weka . core . Instances ;
/* *
* Exemplo simples de visualização com coordenadas paralelas .
*/
public class ExemploPC2
{
public static void main ( String [] args ) throws IOException
{
// Usaremos a base iris . arff .
FileReader reader =
new FileReader (" / home / rafael / Java / APIs / Weka / data / iris . arff " );
Instances instances = new Instances ( reader );
// Criamos o componente com as instâncias , com normalização global , usando cores diferentes
// e com um pequeno " wiggle ".
ParallelCoordinatesComponent component =
new ParallelCoordinatesComponent ( instances , true , true ,0.2);
component . setPreferredSize ( new Dimension (1024 ,500));
// Montamos a UI para visualizar o componente .
JFrame frame = new JFrame ( " Parallel Coordinates " );
frame . getContentPane (). add ( component );
frame . pack ();
frame . setVisible ( true );
frame . setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE );
}
}
Figura 7: Tela da aplicação da classe
http://www.lac.inpe.br/∼rafael.santos
ExemploPC2
(listagem 18).
Rafael Santos
Mineração e Visualização de Dados usando Java
27
Ainda outro exemplo de uso do componente pode ser visto na classe
ExemploPC3
(listagem 19), que mostra
os dados de uma relação dos sobreviventes do naufrágio do Titanic dados que só contém atributos nominais,
portanto com muitos dados ocupando as mesmas posições no espaço de atributos. Para esta visualização usamos
linhas com cores bem mais transparentes e um wiggle bem maior para evitar superposições. O resultado (cópia
da tela) pode ser visto na gura 8.
ExemploPC3,
Listagem 19: A classe
que implementa outro exemplo simples de visualização com coordenadas
paralelas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package parcoords ;
import java . awt .*;
import java . io .*;
import javax . swing . JFrame ;
import weka . core . Instances ;
/* *
* Exemplo simples de visualização com coordenadas paralelas .
*/
public class ExemploPC3
{
public static void main ( String [] args ) throws IOException
{
// Usaremos a base titanic . arff .
FileReader reader =
new FileReader (" / home / rafael / Java / APIs / Weka / data / titanic . arff " );
Instances instances = new Instances ( reader );
// Criamos uma instância de ParallelCoordinatesConstants e modificamos alguns dos
// atributos , como as cores para as linhas e algumas margens .
ParallelCoordinatesConstants consts = new ParallelCoordinatesConstants ();
int transp = 3;
// A ordem em que os valores para a classe aparecem no arquivo . arff é { yes , no }.
consts . linesColors = new Color []{ new Color (0 ,0 ,255 , transp ), new Color (255 ,0 ,0 , transp )};
consts . bottomMargin = 100;
consts . leftMargin = 75;
ParallelCoordinatesComponent component =
new ParallelCoordinatesComponent ( instances , true , true ,1);
component . setConsts ( consts );
component . setPreferredSize ( new Dimension (1200 ,500));
// Montamos a UI para visualizar o componente .
JFrame frame = new JFrame ( " Parallel Coordinates " );
frame . getContentPane (). add ( component );
frame . pack ();
frame . setVisible ( true );
frame . setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE );
}
}
A classe
ExemploPC4
(listagem 20) mostra como criar um gráco de coordenadas paralelas com área plotada
maior do que a tela do computador.
A classe lê a relação
wine.arff
(com dados físico-químicos de vinhos
de três origens diferentes), cria um componente para visualização com dimensões
somente uma parte do gráco (usando
JScrollPanes
4800 × 1200
pixels e mostra
para permitir a visualização interativa de outras partes).
O gráco como um todo é renderizado em uma imagem e armazenado em um arquivo.
Listagem 20:
A classe
ExemploPC4,
que implementa ainda outro exemplo de visualização com coordenadas
paralelas.
1
2
3
4
5
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
28
Mineração e Visualização de Dados usando Java
Figura 8: Tela da aplicação da classe
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
ExemploPC3
(listagem 19).
package parcoords ;
import java . awt .*;
import java . awt . image . BufferedImage ;
import java . io .*;
import javax . imageio . ImageIO ;
import javax . swing .*;
import weka . core . Instances ;
/* *
* Exemplo simples de visualização com coordenadas paralelas . O gráfico será armazenado em um arquivo .
*/
public class ExemploPC4
{
public static void main ( String [] args ) throws IOException
{
// Usaremos a base wine .
FileReader reader =
new FileReader (" / home / rafael / Java / APIs / Weka / data / wine . arff " );
Instances instances = new Instances ( reader );
// Criamos uma instância de ParallelCoordinatesConstants e escolhemos um tamanho
// maior do que a tela do computador .
ParallelCoordinatesComponent component =
new ParallelCoordinatesComponent ( instances , false , true ,0);
component . setPreferredSize ( new Dimension (4800 ,1200));
// Montamos a UI para visualizar o componente .
JFrame frame = new JFrame ( " Parallel Coordinates " );
frame . getContentPane (). add ( new JScrollPane ( component ));
frame . pack ();
frame . setVisible ( true );
frame . setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE );
// Com o componente visível ( mesmo que parcialmente ), criamos uma imagem para
// renderizar seu conteúdo .
BufferedImage im =
( BufferedImage ) component . createImage ( component . getWidth () , component . getHeight ());
Graphics2D graphics = ( Graphics2D ) im . createGraphics ();
component . paintComponent ( graphics );
// Armazenamos o componente renderizado como uma imagem .
ImageIO . write (im ," PNG " , new File (" graficowine . png " ));
}
}
A gura 9 mostra a tela da aplicação implementada na classe
ExemploPC4
(listagem 20), que mostra somente
uma pequena área da plotagem. A gura 10 mostra a imagem criada com o conteúdo do componente (reduzida
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
29
para caber neste texto).
Figura 9: Tela da aplicação da classe
ExemploPC4
(listagem 20).
Figura 10: Imagem correspondente ao componente criado pela classe
7.3
ExemploPC4
(listagem 20).
Possíveis melhorias
Nesta seção vimos uma implementação de um componente para mostrar dados plotados em coordenadas paralelas.
Algumas possíveis melhorias na implementação são:
.
O componente é estático, isto é, não permite interação com o usuário depois de construído. Algumas opções
de interatividade podem permitir a descoberta de correlações ou mesmo a criação de grácos esteticamente
mais agradáveis, como, por exemplo, a reorganização da ordem dos eixos ou eliminação de alguns eixos;
ou modicações em rótulos, cores, etc.
Outra forma de interatividade que pode ser interessante é a possibilidade de obter mais informações ao
clicar em linhas do gráco.
.
Os eixos das coordenadas tem escala linear, pode-se facilmente implementar opções que possibilitem eixos
em escalas logarítmicas (por exemplo) ou invertidas (menores valores em cima; maiores em baixo).
.
Melhorias nas formas de modicação dos atributos do gráco em si (fontes, cores, tamanhos, etc).
.
Não foi implementado tratamento de dados incompletos (ex. instâncias com atributos faltando).
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
30
Mineração e Visualização de Dados usando Java
8
Visualização de Grafos com JUNG
8.1
Introdução
Grafos são representações abstratas de conjuntos de objetos ligados entre si. Os objetos são tradicionalmente
chamados de vértices e as ligações entre eles, de arestas. Vértices e arestas podem representar praticamente qualquer tipo de informação. A análise numérica e/ou visualização de um grafo permite a identicação de padrões,
outliers, associações, agrupamentos, etc.
A API JUNG (Java Universal Network/Graph Framework, [12]) é um conjunto de classes que permite a modelagem, análise e visualização de dados representáveis como grafos ou redes. Neste capítulo veremos exemplos de
uso da API para visualização de dados na forma de grafos. Parte deste capítulo é baseada no tutorial de JUNG [3]
e em trabalhos de Alessandro Oliveira Arantes e Alexandre Donizeti Alves [1, 9].
Para usar as classes da API JUNG é necessário copiar alguns arquivos
referenciar estes arquivos
1. Crie um diretório
use o nome
lib).
.jar
lib
tecla
para um diretório conhecido e
no seu projeto (clique no projeto com o botão direito do mouse e em
2. Copie para este diretório os arquivos
.jar
.jar
em seu projeto. Para fazer isto usando a IDE Eclipse, siga estes passos:
.jar
New/Folder,
necessários (para simplicar pode-se copiar todos os arquivos
parte da distribuição da API JUNG). Ao terminar de copiar, selecione o nome do projeto e clique a
F5
para atualizar os subdiretórios relacionados ao projeto.
3. Inclua estes arquivos no projeto selecionando o projeto, clicando com o botão direito do mouse e escolhendo
Build Path/Configure Build Path. Clique no botão Add JARs e selecione no diálogo o diretório lib
e todos os arquivos .jar dentro dele. Feche todos os diálogos clicando em seus respectivos botões OK.
Outras IDEs devem ser conguradas com outros métodos o que importa é colocar todos os arquivos
.jar
da
distribuição do Jung em um diretório de forma que façam parte dos projetos em desenvolvimento. Consulte a
documentação da sua IDE para informações especícas.
8.2
Implementação
Para visualizar um grafo em JUNG é necessário primeiro criar a estrutura de grafos no código, então criar um
layout para organização dos vértices e arestas e depois criar um componente para exibir o grafo com este layout.
Para grafos mais complexos (ou quando quisermos modicar programaticamente a aparência dos vértices e arestas) pode ser necessário modicar atributos do componente para exibição.
Como um primeiro exemplo vamos construir um grafo simples, com alguns vértices e arestas, com um layout
básico de visualização e com o componente padrão.
SparseMultigraph
A listagem 21 mostra como criar uma instância de
(que permite a representação de grafos esparsos com arestas múltipas entre os vértices),
como adicionar vértices (no exemplo, instâncias de
cionados com o método
addVertex
String)
e arestas (instâncias de
Float).
Vértices são adi-
da classe que implementa o grafo, e que recebe como parâmetro uma
instância da classe denida para os vértices; e arestas são adicionadas com o método
addEdge
que recebe como
parâmetros uma instância da classe denida para a aresta e duas instâncias da classe denida para os vértices,
efetivamente criando uma ligação entre estes vértices. A listagem também mostra como criar um layout básico
(CircleLayout, que posiciona os vértices com espaçamento regular em um círculo) e nalmente como visualizar
este grafo.
Listagem 21: A classe
1
2
3
4
5
Grafo1, que implementa um exemplo simples de visualização de grafos com a API JUNG.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
31
package jung ;
import java . awt . Dimension ;
import javax . swing . JFrame ;
import edu . uci . ics . jung . algorithms . layout . CircleLayout ;
import edu . uci . ics . jung . graph .*;
import edu . uci . ics . jung . visualization . BasicVisualizationServer ;
/* *
* Exemplo simples de visualização de grafos com a API JUNG .
*/
public class Grafo1
{
public static void main ( String [] args )
{
// Criamos um grafo onde os vértices são inteiros e as arestas são Strings .
Graph < Integer , String > grafo = new SparseMultigraph < Integer , String >();
// Vamos incluir alguns vértices no grafo .
for ( int i =1; i <=5; i ++) grafo . addVertex (i );
// Mais um -- veja que vértices não podem ter identificadores repetidos .
grafo . addVertex (12);
grafo . addVertex (1);
grafo . addVertex (4);
// Vamos adicionar algumas arestas ao grafo .
for ( int i =1; i <=5; i ++)
for ( int j=i +1; j <=5; j ++)
grafo . addEdge (" De "+i +" para " +j ,i , j );
// Arestas adicionais -- agora sim , podemos ter mais de uma aresta entre vértices
// contanto que seus identificadores sejam diferentes .
grafo . addEdge (" Outro de " +1+ " para " +2 ,1 ,2);
// Podemos imprimir , no terminal , a lista de vértices e arestas do grafo .
System . out . println ( grafo );
// Para visualizar o grafo precisaremos de um layout correspondente ao tipo do grafo .
CircleLayout < Integer , String > layout = new CircleLayout < Integer , String >( grafo );
layout . setSize ( new Dimension (400 ,400));
// Precisamos também de um componente para visualização .
BasicVisualizationServer < Integer , String > component =
new BasicVisualizationServer < Integer , String >( layout );
component . setPreferredSize ( new Dimension (450 ,450));
// Montamos a UI para visualizar o componente .
JFrame f = new JFrame (" Graph View " );
f. getContentPane (). add ( component );
f. pack ();
f. setVisible ( true );
f. setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE );
}
}
A listagem 22 mostra o resultado da execução da classe
causa da chamada a
System.out.println
Grafo1, da parte
Grafo1).
na linha 39 da classe
que é mostrada no terminal (por
Esta saída (reformatada para este
documento) contém todas as informações sobre vértices e arestas em forma textual.
Listagem 22: Saída da classe
1
2
3
4
Grafo1
(listagem 21).
Vertices :1 ,2 ,3 ,4 ,5 ,12
Edges : Outro de 1 para 2[1 ,2] De 4 para 5[4 ,5] De 1 para 3[1 ,3] De 1 para 2[1 ,2]
De 1 para 5[1 ,5] De 1 para 4[1 ,4] De 2 para 4[2 ,4] De 2 para 3[2 ,3]
De 3 para 4[3 ,4] De 3 para 5[3 ,5] De 2 para 5[2 ,5]
A gura 11 mostra a tela da aplicação implementada na classe
Grafo1
(listagem 21). Nesta gura podemos
observar os vértices e arestas, com um vértice isolado e uma aresta dupla entre dois dos vértices, o que é possível graças ao uso de
SparseMultigraph.
Os vértices são posicionados nas bordas de um círculo, uniformemente.
Para um exemplo mais complexo de grafo, com aspectos visuais diferentes para vértices e arestas dependendo
de seus valores, criaremos uma classe que permite a visualização simples de um grafo de cidades e distâncias
entre as mesmas.
Rafael Santos
Os vértices (cidades) serão do tipo
String
e as arestas do tipo
Float.
Para modicar a
http://www.lac.inpe.br/∼rafael.santos
32
Mineração e Visualização de Dados usando Java
Figura 11: Tela da aplicação da classe
Grafo1
(listagem 21).
aparência dos vértices e arestas precisaremos de transformadores instâncias de classe que implementa a inter-
Transformer<K.V> que mostram como retornar uma instância da classe V em função do valor da instância
K. Por exemplo, para mudar a cor do vértice usaremos uma instância de uma classe que implementa
Transformer<String,Paint> que contém o método transform que mapeia instâncias de String para Paint
face
da classe
determinando assim, na prática, como o vértice será pintado para um determinado valor dele mesmo.
Transformações serão usadas para determinar o preenchimento dos vértices a partir dos valores deles, assim como
o estilo de linha e cor das arestas a partir dos valores destas. Estas transformações serão implementadas em classes
mostradas depois da principal. Além destas transformações denidas pelo programador, algumas transformações
prontas serão usadas para mostrar os rótulos dos vértices e arestas. A classe
Grafo2, na listagem 23, cria o grafo
e popula o mesmo com os vértices e arestas.
Listagem 23: A classe
Grafo2,
que implementa um exemplo customizado de visualização de grafos com a API
JUNG.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt .*;
import javax . swing . JFrame ;
import
import
import
import
edu . uci . ics . jung . algorithms . layout . CircleLayout ;
edu . uci . ics . jung . graph .*;
edu . uci . ics . jung . visualization . BasicVisualizationServer ;
edu . uci . ics . jung . visualization . decorators . ToStringLabeller ;
/* *
* Exemplo simples de visualização de grafos com a API JUNG .
*/
public class Grafo2
{
public static void main ( String [] args )
{
// Criamos um grafo onde os vértices são Strings e as arestas são Floats .
Graph < String , Float > grafo = new SparseMultigraph < String , Float >();
// Vamos definir algumas cidades .
String SJC = " SP / São José dos Campos ";
String CP = " SP / Campinas ";
String RE = " PE / Recife ";
String BE = " PA / Belém " ;
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
33
String VV = " ES / Vila Velha ";
// Vamos incluir alguns vértices no grafo .
grafo . addVertex ( VV );
grafo . addVertex ( SJC );
grafo . addVertex ( CP );
grafo . addVertex ( RE );
grafo . addVertex ( BE );
// Agora algumas arestas entre estes vértices .
grafo . addEdge (153 F , SJC , CP );
grafo . addEdge (2023 F ,BE , RE );
grafo . addEdge (1845 F ,VV , RE );
grafo . addEdge (778 F , SJC , VV );
grafo . addEdge (2604 F ,CP , RE );
grafo . addEdge (993 F ,VV , CP );
// Para visualizar o grafo precisaremos de um layout correspondente ao tipo do grafo .
CircleLayout < String , Float > layout = new CircleLayout < String , Float >( grafo );
layout . setSize ( new Dimension (450 ,450));
// Precisamos também de um componente para visualização .
BasicVisualizationServer < String , Float > component =
new BasicVisualizationServer < String , Float >( layout );
component . setBackground ( Color . WHITE );
component . setPreferredSize ( new Dimension (600 ,500));
// Modificamos os estilos dos vértices e arestas .
component . getRenderContext (). setVertexFillPaintTransformer ( new Grafo2_VertexPaint ());
component . getRenderContext (). setEdgeStrokeTransformer ( new Grafo2_EdgeStroke ());
component . getRenderContext (). setEdgeDrawPaintTransformer ( new Grafo2_EdgePaint ());
component . getRenderContext (). setVertexLabelTransformer ( new ToStringLabeller < String >());
component . getRenderContext (). setEdgeLabelTransformer ( new ToStringLabeller < Float >());
// Montamos a UI para visualizar o componente .
JFrame f = new JFrame (" Graph View " );
f. getContentPane (). add ( component );
f. pack ();
f. setVisible ( true );
f. setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE );
}
}
Na classe
Grafo2 (listagem 23) temos várias chamadas a métodos setXXXTransformer da classe RenderContext,
obtida do componente para visualização. Estes métodos indicam ao renderizador do componente como alguns
dos elementos do grafo serão renderizados no exemplo da classe
Grafo2
temos cinco chamadas a métodos
deste tipo, que indicam que classes serão usadas para determinar preenchimento dos vértices (linha 54), estilo
da linha da aresta (linha 55), cor da linha da aresta (linha 56), rótulo do vértice (linha 57) e rótulo da aresta
(linha 58). Alguns dos métodos
setXXXTransformer
. setArrowDrawPaintTransformer
da classe
RenderContext
são descritos a seguir.
mapeia instâncias da classe da aresta para
Paint
para determinar a
Paint
para determinar a
Stroke
para determinar o
cor do desenho das setas em grafos dirigidos.
. setArrowFillPaintTransformer
mapeia instâncias da classe da aresta para
cor ou modo de preenchimento das setas em grafos dirigidos.
. setEdgeArrowStrokeTransformer
mapeia instâncias da classe da aresta para
estilo de linha do desenho das setas em grafos dirigidos.
. setEdgeDrawPaintTransformer
mapeia instâncias da classe da aresta para
Paint
para determinar a cor
mapeia instâncias da classe da aresta para
Paint
para determinar a cor
do desenho das arestas.
. setEdgeFillPaintTransformer
ou modo de preenchimento das arestas.
. setEdgeStrokeTransformer
mapeia instâncias da classe da aresta para
Stroke
para determinar o estilo
de linha das arestas.
. setEdgeFontTransformer
mapeia instâncias da classe da aresta para
Font
para determinar a fonte do
texto associado às arestas.
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
34
Mineração e Visualização de Dados usando Java
. setEdgeLabelTransformer
mapeia instâncias da classe da aresta para
String
para determinar o texto
associado às arestas.
. setVertexDrawPaintTransformer
mapeia instâncias da classe do vértice para
Paint
para determinar a
mapeia instâncias da classe do vértice para
Paint
para determinar a
cor do desenho dos vértices.
. setVertexFillPaintTransformer
cor ou modo de preenchimento dos vértices.
. setVertexIconTransformer
mapeia instâncias da classe do vértice para
Icon
para determinar um ícone
para desenho dos vértices.
. setVertexShapeTransformer
mapeia instâncias da classe do vértice para
Shape
para determinar uma
forma geométrica para desenho dos vértices.
. setVertexStrokeTransformer
mapeia instâncias da classe do vértice para
Stroke
para determinar o
estilo de linha dos vértices.
. setVertexFontTransformer mapeia instâncias da classe do vértice para Font para determinar a fonte do
texto associado aos vértices.
. setVertexLabelTransformer mapeia instâncias da classe do vértice para String para determinar o texto
associado aos vértices.
Grafo2 (listagem 23). O primeiro (linha 54) mapeia instâncias dos vértices (String) para instâncias de Paint. Para fazer
este mapeamento devemos criar uma instância de classe que implementa Transformer<String,Paint> e usá-la
como parâmetro para o método setVertexFillPaintTransformer. Esta nova classe, Grafo2_VertexPaint,
Para demonstrar como estes mapeamentos são feitos, vejamos os mapeamentos usados na classe
é mostrada na listagem 24.
Listagem 24: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Grafo2_VertexPaint,
que mapeia vértices (String) para
Paint.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt .*;
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia Strings para Paint , definindo como vértices serão preenchidos no grafo .
*/
public class Grafo2_VertexPaint implements Transformer < String , Paint >
{
// Método para mapear Strings para Paint -- usamos o nome do estado para definir uma cor .
public Paint transform ( String s)
{
if (s. startsWith ( " SP " )) return Color . BLUE ;
else if (s. startsWith ( " PE ") || s. startsWith (" PA " )) return Color . MAGENTA ;
else return Color . YELLOW ;
}
}
Para determinar que estilos de linha serão usados para desenhar as linhas que representam as arestas precisaremos
de uma instância de classe que implementa
método
setEdgeStrokeTransformer
Transformer<Float,Stroke>
(linha 55 da listagem 23).
e usá-la como parâmetro para o
Esta nova classe,
Grafo2_EdgeStroke,
é
mostrada na listagem 25.
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
Listagem 25: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
35
Grafo2_EdgeStroke,
que mapeia arestas (Float) para
Stroke.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt .*;
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia Floats para Stroke , definindo os estilos de linhas para arestas no grafo .
*/
public class Grafo2_EdgeStroke implements Transformer < Float , Stroke >
{
// Método para mapear Floats para Stroke -- usamos limiares para determinar os estilos .
public Stroke transform ( Float f)
{
if (f < 800) return
new BasicStroke (1f , BasicStroke . CAP_BUTT , BasicStroke . JOIN_MITER ,
1f , new float []{2 f } ,0 f );
else if (f > 1500) return new BasicStroke (3 f );
else return new BasicStroke (7 f );
}
}
Finalmente, para determinar que cores serão usadas para desenhar as linhas que representam as arestas precisaremos de uma instância de classe que implementa
o método
setEdgeDrawPaintTransformer
Transformer<Float,Paint>
e usá-la como parâmetro para
(linha 56 da listagem 23). Esta nova classe,
Grafo2_EdgePaint,
é
mostrada na listagem 26.
Listagem 26: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Grafo2_EdgePaint,
que mapeia arestas (Float) para
Paint.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt .*;
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia Floats para Paint , definindo as cores das linhas para arestas no grafo .
*/
public class Grafo2_EdgePaint implements Transformer < Float , Paint >
{
// Método para mapear Floats para Paint -- usamos limiares para determinar as cores das linhas .
public Paint transform ( Float f)
{
if (f < 800) return Color . BLACK ;
else if (f > 1500) return Color . BLUE ;
else return Color . RED ;
}
}
ToStringLabeller<String>
setVertexLabelTransformer na linha 57 da listagem 23. Esta chamada fará
com que a representação textual (obtida através do método toString da class que implementa o vértice seja
usada para criar um rótulo para o mesmo. Similarmente, uma nova instância de ToStringLabeller<Float>
será passada como parâmetro para o método setEdgeLabelTransformer (linha 58 da listagem 23) para que
Além das transformações descritas nestas três classes, usamos uma nova instância de
como parâmetro para o método
uma representação textual do valor da aresta seja usado como rótulo para a mesma.
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
36
Mineração e Visualização de Dados usando Java
O resultado da execução da classe
Grafo2 (listagem 23) é mostrado na gura 12.
Os efeitos das transformações
podem ser vistos nas arestas e vértices.
Figura 12: Tela da aplicação da classe
Grafo2
(listagem 23).
Como último exemplo veremos o código necessário para criar um grafo interativo que mostra similaridades entre
publicações de um instituto (os dados para este exemplo foram cedidos por Alessandro Oliveira Arantes, veja [1]).
As publicações foram selecionadas e comparadas duas a duas usando vários critérios, de forma a criar um índice
de similaridade numérico.
Os dados usados para gerar os grafos são apresentados em um arquivo de texto,
reproduzido parcialmente na listagem 27.
Listagem 27: Lista (parcial) de publicações e similaridade entre elas.
1
2
3
4
5
6
7
8
CTA / IEAv - EFO /AP -004/2005 ; 19 ; CTA / IEAv - EFO /AP -010/2005
CTA / IEAv - EFO /AP -010/2005 ; 19 ; CTA / IEAv - EFO /AP -004/2005
CTA / ITA - IEA / TD -010/2003 ; 18 ; CTA / ITA - IEE /TD -001/2003
CTA / ITA - IEE / TD -001/2003 ; 18 ; CTA / ITA - IEA /TD -010/2003
CTA / ITA - IEE / AE -009/2002 ; 17 ; CTA / ITA - IEE /AE -021/2002
...
CTA / ITA - IEA / TC -001/2005 ; 12 ; CTA / ITA - IEE /AE -010/2005
CTA / ITA - IEA / TC -001/2005 ; 12 ; CTA / ITA - IEE /AE -025/2005
Para demonstrar melhor a versatilidade da API JUNG, criaremos classes especícas para representar as arestas
e vértices de um grafo de similaridade entre publicações. A classe
BiblioVertice,
mostrada na listagem 28,
representa um vértice no grafo de similaridades. Esta classe contém métodos que retornam informações sobre
a publicação (departamento, ano, etc.) e métodos para comparação e identicação de vértices únicos (equals,
hashCode).
Listagem 28: A classe
1
2
3
4
5
6
7
8
BiblioVertice, que representa um vértice em uma coleção de documentos e referências.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
/* *
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
37
* Esta classe representa um vértice em um grafo de referências bibliográficas ( um documento ).
* Cada vértice será identificado por uma String . Seria mais fácil usar uma String diretamente ,
* mas com uma classe podemos criar atributos e métodos adicionais interessantes .
*/
public class BiblioVertice implements Comparable < BiblioVertice >
{
// O identificador do documento . Os documentos são identificados por Strings com quatro
// tokens : o primeiro é constante , o segundo é o nome do departamento , o terceiro é o
// índice do documento e o quarto é o ano de publicação .
private String id ;
// Construtor , inicializa os atributos .
public BiblioVertice ( String i)
{
id = i;
}
// Método que verifica se uma instância desta classe é igual a outra -- este método ( criado
// automaticamente pelo Eclipse ) é essencial para algumas operações em grafos .
public boolean equals ( Object obj )
{
if ( this == obj ) return true ;
if ( obj == null ) return false ;
if ( getClass () != obj . getClass ()) return false ;
BiblioVertice other = ( BiblioVertice ) obj ;
if ( id == null )
{
if ( other . id != null ) return false ;
}
else if (! id . equals ( other . id )) return false ;
return true ;
}
// Método que cria o hash code de uma instância da classe -- essencial para criação dos grafos .
// Este método foi criado automaticamente pelo Eclipse .
public int hashCode ()
{
final int prime = 31;
int result = 1;
result = prime * result + (( id == null ) ? 0 : id . hashCode ());
return result ;
}
// Método para comparar uma instância desta classe com outra .
public int compareTo ( BiblioVertice o)
{
return o. id . compareTo ( id );
}
// Retorna o identificador armazenado na classe .
public String getId () { return id ; }
// Retorna o identificador do documento ( terceiro token na identificação ).
public String getDocId ()
{
String [] tokens = id . split ( "/" );
return tokens [2];
}
// Retorna o instituto ( primeira parte do segundo token na identificação ).
public String getInstituto ()
{
String [] tokens1 = id . split ("/" );
String [] tokens2 = tokens1 [1]. split (" -" );
return tokens2 [0];
}
// Retorna o ano da publicação ( quarto token na identificação ).
public int getAno ()
{
String [] tokens = id . split ( "/" );
return Integer . parseInt ( tokens [3]);
}
}
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
38
Mineração e Visualização de Dados usando Java
A classe
BiblioAresta,
mostrada na listagem 29, representa uma aresta entre dois vértices no grafo. Arestas
representam similaridade entre os vértices, e terão associadas a elas um valor numérico (dado pela tabela original
com os dados). Como teremos várias arestas com o mesmo valor, é necessário criar um identicador único para
as arestas, o que é implementado na classe.
Listagem 29: A classe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
BiblioAresta,
que representa uma aresta em uma coleção de documentos e referências.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
/* *
* Esta classe representa uma aresta em um grafo de similaridade entre documentos . Cada aresta terá
* um identificador único e um valor numérico ( peso da similaridade ) associado à aresta .
*/
public class BiblioAresta
{
public static int contadorDeArestas ; // Campo estático para criar o identificador único .
private int id ; // Identificador único .
private double peso ; // Valor da similaridade .
// Construtor , inicializa os campos .
public BiblioAresta ( double p )
{
id = contadorDeArestas ++;
peso = p;
}
// Recupera o valor da similaridade .
public double getPeso () { return peso ; }
}
Com classes para representar aresta e vértice, podemos escrever uma classe que representa um grafo composto
destas arestas e vértices e que permite a visualização e interação com este grafo. A classe
GrafoBiblio, mostrada
na listagem 30, executa os seguintes passos:
.
Cria o grafo como um grafo direcionado (instância de
DirectedSparseMultigraph) e o preenche com os
dados lidos do arquivo (veja listagem 27).
.
Cria um layout para o grafo neste exemplo usaremos uma instância de
SpringLayout2
para controlar o
posicionamento dos vértices. Este layout se organiza após a construção do grafo e durante a visualização
do mesmo, em outras palavras, sua aparência muda do início da visualização para se estabilizar depois de
algum tempo de execução da aplicação da visualização. Outras opções de layout serão apresentadas.
.
Cria o componente para visualização do grafo (instância de
VisualizationViewer) e registra listeners de
eventos de mouse para o componente.
.
Modica dicas para a renderização do grafo no componente e adiciona interatividade padrão com o mouse.
.
Cria os transformadores para controlar a renderização dos vértices e arestas.
Além da interatividade padrão com o mouse teremos a possibilidade de gravar em disco cópias da tela exibida.
Para isto basta clicar com o botão direito do mouse no componente.
Listagem 30: A classe
1
2
3
4
5
6
7
8
GrafoBiblio,
que representa uma aresta em uma coleção de documentos e referências.
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt .*;
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import
import
import
import
import
39
java . awt . RenderingHints . Key ;
java . awt . event .*;
java . awt . image . BufferedImage ;
java . io .*;
java . util . HashMap ;
import javax . imageio . ImageIO ;
import javax . swing . JFrame ;
import
import
import
import
edu . uci . ics . jung . algorithms . layout .*;
edu . uci . ics . jung . graph .*;
edu . uci . ics . jung . visualization . VisualizationViewer ;
edu . uci . ics . jung . visualization . control .*;
/* *
* Esta classe , bem mais completa e complexa , monta um grafo para visualização de uma rede de
* similaridades entre artigos científicos . Cada artigo é representado por uma instância de
* BiblioVertice e as similaridades por instâncias de BiblioAresta .
* A classe implementa algumas técnicas interessantes de visualização e interação com o usuário .
*/
public class GrafoBiblio implements MouseListener
{
// O componente usado para visualização interativa do grafo .
private VisualizationViewer < BiblioVertice , BiblioAresta > component ;
// Construtor da classe , cria o grafo e as estruturas de layout e visualização .
public GrafoBiblio () throws IOException
{
// Nosso grafo será direcionado .
Graph < BiblioVertice , BiblioAresta > graph =
new DirectedSparseMultigraph < BiblioVertice , BiblioAresta >();
// Vamos ler todas as entradas na tabela de similaridade entre documentos de um arquivo .
BufferedReader br =
new BufferedReader ( new FileReader ("/ home / rafael / Java / DM / Graphs / Alessandro / tabela4 . txt " ));
while ( true )
{
// Linha por linha ...
String line = br . readLine ();
if ( line == null ) break ;
// Quebra a linha em seus tokens .
String [] tokens = line . split (";" );
// Cada linha contém dois vértices e uma aresta : adicionamos ao grafo .
BiblioVertice bv1 = new BiblioVertice ( tokens [0]. trim ());
BiblioVertice bv2 = new BiblioVertice ( tokens [2]. trim ());
BiblioAresta ba = new BiblioAresta ( Integer . parseInt ( tokens [1]. trim ()));
graph . addVertex ( bv1 );
graph . addVertex ( bv2 );
graph . addEdge (ba , bv1 , bv2 );
}
br . close ();
// Para visualizar o grafo precisaremos de um layout adequado -- existe amplo espaço para
// experiências aqui .
SpringLayout2 < BiblioVertice , BiblioAresta > layout =
new SpringLayout2 < BiblioVertice , BiblioAresta >( graph );
layout . setSize ( new Dimension (700 ,700));
// Precisamos também de um componente para visualização . Usaremos um VisualizationViewer
// que permite interação com o usuário .
component = new VisualizationViewer < BiblioVertice , BiblioAresta >( layout );
component . setPreferredSize ( new Dimension (750 ,750));
component . setBackground ( Color . WHITE );
component . addMouseListener ( this );
// Queremos uma boa qualidade de renderização , com antialiasing e transparência .
HashMap < Key , Object > renderingHints = new HashMap < Key , Object >();
renderingHints . put ( RenderingHints . KEY_ALPHA_INTERPOLATION ,
RenderingHints . VALUE_ALPHA_INTERPOLATION_QUALITY );
renderingHints . put ( RenderingHints . KEY_ANTIALIASING , RenderingHints . VALUE_ANTIALIAS_ON );
component . setRenderingHints ( renderingHints );
// Adicionamos interatividade básica com o mouse para este componente .
DefaultModalGraphMouse < BiblioVertice , BiblioAresta > gm =
new DefaultModalGraphMouse < BiblioVertice , BiblioAresta >();
gm . setMode ( ModalGraphMouse . Mode . TRANSFORMING );
component . setGraphMouse ( gm );
// Criamos os transformadores para modificar a aparência dos vértices e arestas .
component . getRenderContext (). setVertexFillPaintTransformer ( new GrafoBiblio_BiblioVerticePaint ());
component . getRenderContext (). setVertexShapeTransformer ( new GrafoBiblio_BiblioVerticeShape ());
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
40
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
Mineração e Visualização de Dados usando Java
component . getRenderContext (). setVertexLabelTransformer ( new GrafoBiblio_BiblioVerticeLabel ());
component . getRenderContext (). setEdgeStrokeTransformer ( new GrafoBiblio_BiblioArestaStroke ());
component . getRenderContext (). setEdgeDrawPaintTransformer ( new GrafoBiblio_BiblioArestaPaint ());
// Montamos a UI para visualizar o componente .
JFrame f = new JFrame (" Graph View " );
f. getContentPane (). add ( component );
f. pack ();
f. setVisible ( true );
f. setDefaultCloseOperation ( JFrame . EXIT_ON_CLOSE );
}
// Se o mouse for clicado ( independente da interatividade já registrada com o componente ),
// verificaremos o botão clicado para gravar , em uma imagem , um snapshot do componente .
public void mouseClicked ( MouseEvent e)
{
if (e. getButton () == MouseEvent . BUTTON3 )
{
// Criamos uma imagem do tamanho do componente de visualização .
BufferedImage im = ( BufferedImage ) component . createImage ( component . getWidth () ,
component . getHeight ());
// Pegamos seu contexto gráfico .
Graphics graphics = im . createGraphics ();
// Desenhamos o componente na imagem .
component . paintComponents ( graphics );
component . update ( graphics );
// Gravamos a imagem .
try
{
ImageIO . write (im ," PNG " , new File (" bibgraph . png " ));
}
catch ( IOException e1 )
{
e1 . printStackTrace ();
}
}
}
// Métodos requeridos pela interface MouseListener .
public void mouseEntered ( MouseEvent arg0 ) { }
public void mouseExited ( MouseEvent arg0 ) { }
public void mousePressed ( MouseEvent arg0 ) { }
public void mouseReleased ( MouseEvent arg0 ) { }
// Ponto de entrada da aplicação .
public static void main ( String [] args ) throws IOException
{
new GrafoBiblio ();
}
}
A classe
GrafoBiblio
(listagem 30) usa cinco transformações para modicar a aparência dos vértices e arestas,
que são implementadas por classes especiais.
A primeira destas classes contém um método que determina as
formas geométricas que serão usadas para desenho dos vértices. Cada vértice será desenhado com uma forma
determinada a partir do instituto que gerou a publicação correspondente ao vértice. O código para determinação
das formas geométricas é implementado na classe
Listagem 31: A classe
GrafoBiblio_BiblioVerticeShape,
na listagem 31.
GrafoBiblio_BiblioVerticeShape, que determina que formas geométricas serão usadas
para desenhar vértices.
1
2
3
4
5
6
7
8
9
10
11
12
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import
import
import
import
java . awt . Polygon ;
java . awt . Shape ;
java . awt . geom . Ellipse2D ;
java . awt . geom . Rectangle2D ;
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
41
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia BiblioVertice para Shape , definindo como vértices serão desenhados no grafo .
*/
public class GrafoBiblio_BiblioVerticeShape implements Transformer < BiblioVertice , Shape >
{
// Método para mapear BiblioVertice para Shape -- usamos o instituto que publicou o documento .
public Shape transform ( BiblioVertice a )
{
if (a. getInstituto (). equals ( " IEAv " )) // Estrela
{
int [] x = new int []{0 ,4 ,12 ,4 ,0 , -4 , -12 , -4};
int [] y = new int []{ -12 , -4 ,0 ,4 ,12 ,4 ,0 , -4};
return new Polygon (x ,y , x. length );
}
else if (a. getInstituto (). equals ( " ITA " )) // Círculo
{
return new Ellipse2D . Float ( -12 , -12 ,24 ,24);
}
else if (a. getInstituto (). equals ( " IAE " )) // Losango
{
int [] x = new int []{0 ,12 ,0 , -12};
int [] y = new int []{ -12 ,0 ,12 ,0};
return new Polygon (x ,y , x. length );
}
else if (a. getInstituto (). equals ( " VDR " )) // Quadrado
{
return new Rectangle2D . Float ( -10 , -10 ,20 ,20);
}
else return new Ellipse2D . Float ( -3 , -3 ,6 ,6); // Círculo pequeno
}
}
Outra modicação no grafo deste exemplo será a escolha da cor de preenchimento do vértice. Usaremos o ano da
publicação, com uma tabela de cores denida pela classe
Listagem 32: A classe
GrafoBiblio_BiblioVerticePaint,
GrafoBiblio_BiblioVerticePaint,
na listagem 32.
que determina que cores serão usadas para pintar
vértices.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt .*;
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia BiblioVertice para Paint , definindo como vértices serão preenchidos no grafo .
*/
public class GrafoBiblio_BiblioVerticePaint implements Transformer < BiblioVertice , Paint >
{
// Método para mapear BiblioVertice para Paint -- usamos o ano da publicação .
public Paint transform ( BiblioVertice v )
{
Color c = Color . LIGHT_GRAY ;
switch (v . getAno ())
{
case 2001: c = Color . RED ; break ;
case 2002: c = Color . ORANGE ; break ;
case 2003: c = Color . YELLOW ; break ;
case 2004: c = Color . GREEN ; break ;
case 2005: c = Color . CYAN ; break ;
}
return c;
}
}
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos
42
Mineração e Visualização de Dados usando Java
A terceira modicação na aparência dos vértices será a dos rótulos que serão desenhados junto dos vértices.
Ao invés de usarmos toda a informação sobre documentos, usaremos somente os identicadores simples. Esta
modicação é controlada pela classe
Listagem 33: A classe
GrafoBiblio_BiblioVerticeLabel
GrafoBiblio_BiblioVerticeLabel,
(listagem 33).
que determina que rótulos serão desenhados junto
aos vértices.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia BiblioVertice para String , definindo como vértices serão identificados
* ( com rótulos ) no grafo .
*/
public class GrafoBiblio_BiblioVerticeLabel implements Transformer < BiblioVertice , String >
{
// Método para mapear BiblioVertice para String -- usamos o identificador do documento .
public String transform ( BiblioVertice v)
{
return v. getDocId ();
}
}
Finalmente, teremos duas modicações na aparência das arestas. A primeira será o estilo da linha da aresta entre
dois vértices (duas publicações), que será determinado pelo peso ou similaridade entre estas duas publicações.
O mapeamento do peso para o tipo de linha será feito pela classe
GrafoBiblio_BiblioArestaStroke,
na
listagem 34.
Listagem 34: A classe
GrafoBiblio_BiblioArestaStroke,
que mapeia similaridade entre vértices para estilo
de linha das arestas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt . BasicStroke ;
import java . awt . Stroke ;
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia BiblioAresta para Stroke , definindo estilos de linha para arestas no grafo .
*/
public class GrafoBiblio_BiblioArestaStroke implements Transformer < BiblioAresta , Stroke >
{
// Método para mapear BiblioAresta para Stroke -- usamos o peso da similaridade .
public Stroke transform ( BiblioAresta a )
{
double espessura = Math . max (0 , a. getPeso () -10);
return new BasicStroke (( float )( espessura ));
}
}
A última transformação para modicar a aparência dos vértices e arestas determina a cor da aresta também em
função da similaridade entre os vértices, e é implementada pela classe
GrafoBiblio_BiblioArestaPaint,
na
listagem 35.
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
Listagem 35: A classe
43
GrafoBiblio_BiblioArestaPaint,
que mapeia similaridade entre vértices para cor de
linha das arestas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* Parte do treinamento em Mineração e Visualização de Dados usando Java
* Rafael Santos ( rafael . santos@lac . inpe . br )
* http :// www . lac . inpe . br /~ rafael . santos /
*/
package jung ;
import java . awt .*;
import org . apache . commons . collections15 . Transformer ;
/* *
* Esta classe mapeia BiblioAresta para Paint , definindo cor de linha para arestas no grafo .
*/
public class GrafoBiblio_BiblioArestaPaint implements Transformer < BiblioAresta , Paint >
{
// Método para mapear BiblioAresta para Paint -- usamos o peso da similaridade .
public Paint transform ( BiblioAresta a)
{
if (a. getPeso () >= 18) return new Color (0 ,0 ,210);
else if (a. getPeso () >= 15) return new Color (0 ,0 ,105);
else return Color . BLACK ;
}
}
Alguns dos resultados da execução da classe
GrafoBiblio (listagem 30) (com demonstração das interações com
usuário) são mostrados nas guras a seguir.
Figura 13: Grafo de publicações e similaridades con-
Figura 14: Grafo de publicações e similaridades con-
guração inicial.
guração mais estável.
Uma apresentação sobre os tipos de layout e suas principais características está fora do escopo deste documento,
mas para comparação, seguem guras do mesmo grafo renderizados com diferentes tipos de layout implementados na API JUNG para mudar o tipo de layout basta modicar a classe usada nas linhas 61 e
GrafoBiblio
Rafael Santos
62 da classe
(listagem 30). Para mais informações verique a documentação on-line em [12].
http://www.lac.inpe.br/∼rafael.santos
44
Mineração e Visualização de Dados usando Java
Figura 15: Ampliação de um setor do grafo, mostrando
Figura 16: Ampliação de outro setor do grafo.
o que podem ser cliques de publicações.
Figura 17:
O gráco pode ser girado em torno do
centro do componente, preservando as orientações dos
Figura 18: O grafo pode ser inclinado, também preservando orientações dos rótulos.
rótulos.
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
Figura 19: Grafo renderizado com o layout implementado pela classe
CircleLayout.
Figura 21: Grafo renderizado com o layout implementado pela classe
Rafael Santos
FRLayout.
45
Figura 20: Grafo renderizado com o layout implementado pela classe
ISOMLayout.
Figura 22: Grafo renderizado com o layout implementado pela classe
FRLayout2.
http://www.lac.inpe.br/∼rafael.santos
46
Mineração e Visualização de Dados usando Java
Figura 23: Grafo renderizado com o layout implementado pela classe
KKLayout.
Figura 24: Grafo renderizado com o layout implementado pela classe
SpringLayout.
É importante observar que alguns destes layouts não mostram de forma imediata os vértices e arestas organizados,
demorando algum tempo para organizar os vértices e arestas até uma conguração estável, o que pode causar
longos tempos de execução em aplicações que tem muitos vértices e/ou arestas.
http://www.lac.inpe.br/∼rafael.santos
Rafael Santos
Mineração e Visualização de Dados usando Java
47
Referências
[1] Alessandro
cas
Oliveira
baseado
em
Arantes.
avaliação
Um
de
sistema
de
conteúdo.
recomendação
de
publicações
cientí-
http://mtc-m18.sid.inpe.br/rep/sid.inpe.br/mtc-
m18@80/2009/10.01.19.32?languagebutton=pt-BR, 2009. Visitado em Fevereiro de 2010.
[2] R. Beale e T. Jackson. Neural Computing: An Introduction. MIT Press, 1990.
[3] Greg Bernstein. JUNG 2.0 Tutorial. http://www.grotto-networking.com/JUNG/JUNG2-Tutorial.pdf, 2010.
Visitado em Março de 2010.
[4] Laurene V. Fausett. Fundamentals of Neural Networks. Prentice Hall, 1994.
[5] A. Inselbert e B. Dimsdale.
Parallel Coordinates for Visualizing Multi-Dimensional Geometry.
Em Proc.
Computer Graphics International Conference, 1987.
[6] Carl G. Looney. Pattern Recognition Using Neural Networks. Oxford University Press, 1st edição, 1997.
[7] J. Ross Quinlan. C4.5: Programs for Machine Learning. Morgan Kaufmann, 1993.
[8] Rafael Santos. Introdução à Programação Orientada a Objetos Usando Java. Campus, 2003.
[9] Rafael Santos.
Material da disciplina CAP-359:
Princípios e Aplicações de Mineração de Dados.
http://www.lac.inpe.br/∼rafael.santos/cap359.jsp, 2010. Visitado em Fevereiro de 2010.
[10] Rafael
Santos.
Material
de
Apresentações
e
Tutoriais
sobre
a
Linguagem
Java.
http://www.lac.inpe.br/∼rafael.santos/javaapresentacoes.jsp, 2010. Visitado em Fevereiro de 2010.
[11] Rafael
Santos.
Material
de
Apresentações
e
Tutoriais
sobre
Mineração
de
Dados.
http://www.lac.inpe.br/∼rafael.santos/dmapresentacoes.jsp, 2010. Visitado em Fevereiro de 2010.
[12] Jung
Development
Team.
Jung
Java
Universal
Network/Graph
Framework.
http://jung.sourceforge.net/index.html, 2010. Visitado em Fevereiro de 2010.
[13] Ian
H.
Witten
e
Eibe
Frank.
Weka
the
waikato
environment
for
knowledge
analysis.
http://www.cs.waikato.ac.nz/ml/weka/. Visitado em Março de 2010.
[14] Ian H. Witten e Eibe Frank.
Data Mining - Practical Machine Learning Tools and Techniques.
Morgan
Kaufmann Publishers, 2nd edição, 2005.
Rafael Santos
http://www.lac.inpe.br/∼rafael.santos