#oBomProgramador #codeDuel #DevXFit #Java
Uma implementação de histograma com Stream API! Vou mostrar com usar a Stream API e AWT/Swig para criar histogramas como este.
Como criar histogramas
Um histograma é
baseado em uma tabela de frequências, ou seja, a quantidade de
ocorrências por faixas ou classes de valores.
Para começar,
precisamos avaliar nossa amostra e determinar em quantas classes
desejamos dividir os dados. Muitas classes podem tornar o histograma
confuso, poucas classes podem ocultar informações importantes.
Existem várias heurísticas para sugerir a quantidade de classes,
como esta:
-
abaixo de 50 dados de amostra, de 5 a 7 classes;
-
entre 50 e 100, de 6 a 10 classes;
-
entre 100 e 250, de 7 a 12 classes;
-
acima de 250 dados de amostra, de 10 a 20 classes.
Porém, esta
heurística pode gerar alguns problemas, dependendo da amplitude e do
tipo de valores. Valores discretos, valores contínuos, variações
pequenas etc.
Pode fazer mais
sentido tentar estabelecer as faixas de valores de acordo com a
natureza dos dados. Vamos supor o exemplo em questão: Médias dos
alunos. Faz sentido termos 10 classes de média? Certamente não.
Teríamos uma granularidade muito baixa e valores dispersos. Faz
sentido dividirmos em 2 classes? Talvez se o objetivo for mostrar
apenas os aprovados e reprovados, sem analisar mais nada.
Podemos pensar em um
belo número de classes para este problema: 5, agrupando as notas em:
-
Péssimo: [0, 2];
-
Ruim: (2, 4];
-
Regular: (4, 6];
-
Bom: (6, 8];
-
Ótimo: (8, 10];
O código-fonte e os arquivos estão no repositório:
Criar um histograma
em planilha é muito simples. Você pode usar a função “frequency”
para isso, conforme demonstra este post:
https://wikieducator.org/OpenOffice/Calc_3/Histogram
Mas eu quis criar
uma demonstração realmente interessante, e a planilha anexa faz
isto.
Agora, como fazer
isso em Java? E, ainda por cima, usando a Stream API?
Implementação
A classe
Histogram.java faz exatamente isso: Gera um conjunto aleatório de
100 notas, arredonda para 2 casas decimais, e calcula a frequência
nas faixas determinadas. Eu usei a raiz 42 na classe Random() para
fixar a geração de números. Eis a frequência das classes de
médias:
{0.0=16, 2.0=21,
4.0=24, 6.0=20, 8.0=19}
Para começar, eu
criei um mapa (na verdade um TreeMap, que é ordenado) com a nota
inferior da faixa e a quantidade de alunos. Isto foi feito com este
único comando:
TreeMap<Double,
Integer> map = new TreeMap<Double, Integer>(Stream.of(new
Object[][] {
{0.0, 0},{2.0,
0},{4.0, 0},{6.0, 0},{8.0, 0}
}).collect(Collectors.toMap(data
-> (Double) data[0], data -> (Integer) data[1])));
Não se impressione
muito, pois é apenas uma maneira “fashion” de inicializar um
mapa. Cada entrada tem a média inferior da classe e a quantidade de
alunos, que começa em zero.
Depois, eu criei a
lista de notas, arredondando em 2 decimais:
List<Double>
averages = (new Random(42)).doubles(100, 0, 1)
.map(n ->
BigDecimal.valueOf(n*10).setScale(2,
RoundingMode.HALF_UP).doubleValue())
.boxed().collect(Collectors.toList());
Parece muita coisa,
mas não é. O método “doubles()”, da classe Random gera um
vetor com “n” elementos entre o inicial e o final. Eu escolhi
entre zero e um, pois vou multiplicar por 10 e arredondar em 2
decimais. O resultado é algo assim:
7.28, 6.83, 3.09,
2.77, 6.66, 9.03, 3.69, 2.76, 4.64,…
O interessante aqui
é o uso da classe BigDecimal para arredondar, além do “boxed()”,
já que o array gerado precisa ser empacotado em Double []. Note que
usei um “collector” para gerar uma lista.
Finalmente, a parte
onde a “mágica” acontece. Eu distribuo a frequência das médias
de acordo com o limite inferior com este código:
averages.forEach(average
-> {
Double key =
map.floorKey(average);
map.put(key,
map.get(key) + 1);
});
É exatamente este
código que impede o programa de ficar totalmente funcional, já que
ele usa um método “side effect” que altera o estado do programa
(a variável “map”). Tive que usar isso devido ao método
“floorKey()”. Este método maravilhoso encontra a primeira chave
no mapa que seja menor ou igual a um valor dado. Então, eu agrupo as
notas das faixas desta maneira.
Plotando o
histograma
Quem programa em
Java sabe que lidar com UI é uma tarefa problemática. É claro que
existem componentes e bibliotecas para lidar com gráficos, e existe
o Java FX. Eu queria fazer algo bem simples e não perder tempo com a
renderização, então, procurei um código pronto em AWT/Swing que
gera um gráfico de barras. Eu o encontrei neste artigo:
E gera um gráfico
bem interessante:
Legal, não? Agora,
tente melhorar este código! Você fez diferente? Tente gerar 100
notas com raiz 42 e veja se o gráfico ficou igual!
Cleuton Sampaio,
M.Sc.
Nenhum comentário:
Postar um comentário