Altair é uma bilioteca de visualizações estatísticas declarativas para Python. Desse jeito Altair foca no que fazer e não como fazer. A ideia principal da biblioteca é que o designer da visualização especifique a relação dos dados e deixar todas as outras decisões automáticas.
A biblioteca é baseada no Vega-Lite, uma gramática poderosa e concisa para construir visualizações rapidamente, e gera gramáticas para renderização em ambientes como Jupyter Notebooks, Jupyter Lab e Colab. De modo geral, vamos utilizar o Altair para facilitar a análise exploratória de dados com uma gramática concisa porém expressiva que especifique gráficos interativos com múltiplas visões.
Sinta-se a vontade para modificar os exemplos, esse é um documento vivo!
Para começar, vamos importar as bibliotecas.
import altair as alt
import pandas as pd
Os dados em Altair são baseados no formato DataFrame do Pandas
, que é simplesmente um conjunto de dados separados por colunas. Essas colunas podem ser chamadas dimensões, campos ou atributos.
A utilização de um dataset é muito simples simples, aceitando um DataFrame carregado localmente ou um formato de dados na web através de uma URL.
dados = pd.read_csv("https://raw.githubusercontent.com/tiagodavi70/vl-altair-tutorial/master/datasets/dados.csv")
dados.head()
Cidade | Data | Precipitação | Pressão Atmosférica ao nível da estação | Pressão Atmosférica máxima | Pressão Atmosférica mínima | Radiação Global | Temperatura do ar - bulbo seco | Temperatura do ponto de orvalho | Temperatura máxima | Temperatura mínima | Temperatura orvalho máxima | Temperatura orvalho mínima | Umidade Relativa máxima | Umidade Relativa mínima | Umidade Relativa do Ar | Direção Horária do Vento | Rajada Máxima de Vento | Velocidade Horária do Vento | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Altamira | 2019-01-06 00:00:00+00:00 | 0.280556 | 989.965278 | 990.274306 | 989.647917 | 894.125641 | 25.822917 | 22.869444 | 26.330556 | 25.337500 | 23.331944 | 22.501389 | 87.416667 | 82.006944 | 84.631944 | 110.173611 | 3.634028 | 1.013889 |
1 | Altamira | 2019-01-13 00:00:00+00:00 | 0.604762 | 990.213690 | 990.492262 | 989.927381 | 823.916484 | 24.428571 | 22.095833 | 24.861905 | 24.016071 | 22.498214 | 21.716667 | 90.005952 | 85.172619 | 87.684524 | 126.190476 | 2.862500 | 0.835714 |
2 | Altamira | 2019-01-20 00:00:00+00:00 | 0.103571 | 990.877381 | 991.207143 | 990.555357 | 828.782418 | 24.779762 | 22.383333 | 25.240476 | 24.330357 | 22.810714 | 21.984524 | 89.684524 | 84.553571 | 87.190476 | 137.708333 | 2.890476 | 0.848214 |
3 | Altamira | 2019-01-27 00:00:00+00:00 | 0.304762 | 990.875595 | 991.179762 | 990.569048 | 882.498901 | 25.006548 | 22.736905 | 25.505357 | 24.580357 | 23.158333 | 22.370238 | 90.214286 | 85.214286 | 87.803571 | 126.994048 | 3.219643 | 0.882738 |
4 | Altamira | 2019-02-03 00:00:00+00:00 | 0.094048 | 989.825595 | 990.120238 | 989.521429 | 956.195604 | 25.751786 | 22.675595 | 26.236905 | 25.324405 | 23.132143 | 22.314286 | 86.642857 | 81.321429 | 84.029762 | 107.065476 | 3.315476 | 0.951190 |
Também é possível criar dataframes de uma lista de dicionários. Na lista abaixo temos a listagem de todos os estados da federação com suas regiões e populações estimadas em julho de 2019. Vamos usar esse conjunto e o arquivo dados.csv
para exemplificar os conceitos do Altair nesse notebook.
estados = [ {'estado': 'São Paulo', 'população': 45919049, "região": "Sudeste"}, {'estado': 'Minas Gerais', 'população': 21168791, "região": "Sudeste"},
{'estado': 'Rio de Janeiro', 'população': 17264943, "região": "Sudeste"}, {'estado': 'Bahia', 'população': 14873064, "região": "Nordeste"},
{'estado': 'Paraná', 'população': 11433957, "região": "Sul"}, {'estado': 'Rio Grande do Sul', 'população': 11377239, "região": "Sul"},
{'estado': 'Pernambuco', 'população': 9557071, "região": "Nordeste"}, {'estado': 'Ceará', 'população': 9132078, "região": "Nordeste"},
{'estado': 'Pará', 'população': 8602865, "região": "Norte"}, {'estado': 'Santa Catarina', 'população': 7164788, "região": "Sul"},
{'estado': 'Maranhão', 'população': 7075181, "região": "Nordeste"}, {'estado': 'Goiás', 'população': 7018354, "região": "Centro-Oeste"},
{'estado': 'Amazonas', 'população': 4144597, "região": "Norte"}, {'estado': 'Espírito Santo', 'população': 4018650, "região": "Sudeste"},
{'estado': 'Paraíba', 'população': 4018127, "região": "Nordeste"}, {'estado': 'Rio Grande do Norte', 'população': 3506853, "região": "Nordeste"},
{'estado': 'Mato Grosso', 'população': 3484466, "região": "Centro-Oeste"}, {'estado': 'Alagoas', 'população': 3337357, "região": "Nordeste"},
{'estado': 'Piauí', 'população': 3273227, "região": "Nordeste"}, {'estado': 'Distrito Federal', 'população': 3015268,"região": "Centro-Oeste"},
{'estado': 'Mato Grosso do Sul', 'população': 2778986, "região": "Centro-Oeste"}, {'estado': 'Sergipe', 'população': 2298696, "região": "Nordeste"},
{'estado': 'Rondônia', 'população': 1777225, "região": "Norte"}, {'estado': 'Tocantins', 'população': 1572866, "região": "Norte"},
{'estado': 'Acre', 'população': 881935, "região": "Norte"}, {'estado': 'Amapá', 'população': 845731, "região": "Norte"},
{'estado': 'Roraima', 'população': 605761, "região": "Norte"} ]
df = pd.DataFrame(estados)
df.shape
(27, 3)
3 colunas e 27 linhas, com isso vamos criar nosso primeiro gráfico.
O objeto fundamental em Altair é o Chart
, que aceita um dataframe ou URL como argumento:
chart = alt.Chart(df)
Essa é estrutura basica para começar a mapear as váriaveis visuais com os dados.
Com o objeto do gráfico em mãos, agora podemos definir o que queremos visualizar, começando com a definição da marca visual que nós queremos associar aos dados. Em Altair isso é possível usando o atributo mark
do objeto usando métodos do tipo Chart.mark_*
. No exemplo abaixo temos a marca ponto usando Chart.mark_point()
:
alt.Chart(df).mark_point()
Todos os pontos foram renderizados um em cima do outro, já que não definimos nada quanto a suas posições. Para separar eles, podemos mapear as dimensões dos dados para uma váriavel visual. Por exemplo, mapear a região do estado para a váriavel visual de posição y
, que representa a posição y
dos pontos. Utilizamos o método encode
para isso:
alt.Chart(df).mark_point().encode(
y='região',
)
O método encode()
constrói um mapeamento chave-valor entre as váriaveis visuais (x, y, cor, forma, tamanho, etc.) e os campos dos dados, acessíveis pelo nome do campo. Usando os dataframes do Pandas, Altair tenta inferir automaticamente os tipos de dados.
Agora podemos separar os pontos que estão agrupados nas regiões, mapeando também a população.
alt.Chart(df).mark_point().encode(
x='população',
y='região',
)
Conseguimos ver uma concentração de estados na região norte com baixa população, e um dos estados tem muito mais população que todos os outros.
Clicando nos três pontinhos ao lado do gráfico abre algumas opções, e uma delas é importante para fins de debug, o editor web do Vega. A gramática gerada é enviada junto com os dados para uma outra página, então menos que seu conjunto de dados seja pequeno ou uma URL tente não fazer isso com frequência. Pode tentar com o gráfico acima e ver como fica em formato de gramática.
O componente principal em uma visualização eficiente são os dados. O que é possível fazer com eles e que tipo de mapeamento visual é adequado?
Altair suporta alguns tipos de dados básicos, e esses tipos podem guiar as decisões de design na visualização, sendo indicado com <campo>:*
.
alt.Chart(df).mark_point().encode(
x='população:Q', # adicionando o tipo, no exemplo anterior Altair usou o tipo reconhecido pelo Pandas
y='região:N',
)
Dados nominais (também conhecidos como dados categóricos) são nomes de categoria.
A utilização de dados nominais indica que valores podem ser iguais ou diferentes um dos outros, e as variáveis visuais que podem ajudar nessa tarefa são: posição e matiz da cor. Usar uma variável visual como tamanho pode gerar confusão, já que tamanho indica ordem ou magnitude.
Dados ordinais são valores que tem uma ordem inerente.
Quando utilizamos dados ordinais, se espera uma percepção de ordem. Posição, tamanho, brilho são variáveis visuais apropriadas para dados ordiais, enquanto matiz de cor pode gerar confusão, já que não tem ordem natural.
Dados quantitativos são valores númericos. Existem alguns tipos de dados quantitativos:
Um intervalo mede a distância entre dois pontos, normalmente utilizado para comparação de unidade, por exemplo: "A é 12 unidades maior que B".
Uma razão mede proporções entre pontos ou fatores de escala. Perguntas possíveis com razão envolvem quanto um ponto é maior que outro ou a proporção entre dois pontos.
Valores quantitativos podem ser mapeados para posição, tamanho, ou matiz, por exemplo. Um eixo começando em 0 é essential para comparação de razões, mas pode ser omitido para comparação de intervalos.
Valores temporais medem pontos ou intervalos no tempo. É um tipo especial de valor quantitativo, mas devido a riqueza de contexto e semântica (separação hierárquica, calendários, representação não uniformes) é considerado um tipo totalmente separado. O tipo temporal em Vega-Lite suporta várias transformações temporais (dia, meses, horas, minutos).
Exemplos de valores temporais incluem strings de datas como “2019-01-04” e “Jan 04 2019”, assim como o padrão ISO para formato de datas:
`“2019-01-04T17:50:35.643Z”`. Usando Pandas as strings podem ser convertidas para o formato datetime de Python.
A função principal do Altair é realizar o mapeamento dos dados (com seus respectivos campos e tipos) com as variáveis visuais. Algumas váriaveis visuais que vamos ver aqui nesse notebook incluem:
x
: Posição horizontal (eixo x).y
: Posição vertical (eixo y).size
: Tamanho, pode ser area ou largura, dependendo da marca visual.color
: Cor válida em CSS.opacity
: Opacidade, vai de 0 (totalmente transparente) até 1 (totalmente opaco).tooltip
: Um espaço de texto para mostrar quando o ponteiro passa por cima.order
: Ordem de desenho.column
: Faceta dos dados em subdivisões horizontais.row
: Faceta dos dados em subdivisões horizontais.Quanto as marcas visuais:
mark_area()
- Uma área com topo e fundo.mark_bar()
- Barras retangulares.mark_circle()
- Círculos com preechimento.mark_line()
- Segmentos de linha conectados.mark_point()
- Pontos com forma configurávels.mark_rect()
- Retângulos, útil para heatmaps.mark_rule()
- Linha horizontal ou vertical através do eixo.mark_square()
- Quadrados com preenchimento.mark_text()
- Texto.mark_tick()
- Um pequeno segmento de linha, horizontal ou vertical.Aqui vamos ver alguns exemplos de gráficos mais simples, enquanto apresentamos a combinação das marcas e variáveis visuais. Para isso vamos usar dois conjuntos de dados, o de população dos estados da federação e o arquivo local `dados.csv`, com os dados do INMET do ano de 2019 do estado do Pará por semana, agrupando os valores dos sensores pela média.
Gráfico de barras
alt.Chart(df).mark_bar().encode(
x="estado",
y="população"
)
O que acontece se trocar os as variáveis visuais x
e y
?
Gráficos de Linhas
alt.Chart(dados).mark_line().encode(
x="Data:T",
y="Precipitação",
color="Cidade" # Mapeando cor pela cidade
)
Em todo as cidades o nível de precipitação diminui no verão, como esperado.
O que acontece se não especificar o tipo temporal?
Gráfico de Área
alt.Chart(dados).mark_area().encode(
x="Data:T",
y="Umidade Relativa do Ar",
color="Cidade:N",
tooltip="Cidade" # passe o mouse em cima das áreas e agora aparece o nome das cidades
)
Empillhando as áreas se percebe que nem todas as medições estão sendo feitas em todo momento, tem muitos buracos.
O que acontece se trocar a cor do tipo nominal para ordinal?
Gráfico de dispersão - Scatterplot
alt.Chart(dados).mark_circle(color="blue").encode(
alt.X("Temperatura do ar - bulbo seco:Q"),
alt.Y("Pressão Atmosférica ao nível da estação:Q"),
alt.Fill("Temperatura mínima"),
alt.Size("Rajada Máxima de Vento"),
alt.OpacityValue(0.3),
tooltip=[alt.Tooltip("Cidade"),
alt.Tooltip("Data")]
)
Sem indicação, as escalas estão começando do zero, mesmo com valores bem afastados. Um jeito de reverter isso é trocando os argumentos `x` e `y` para `alt.X` e `alt.Y`, que permitem mudanças relacionados a variável visual. Vamos indicar que queremos a escala a partir dos valores, e não do zero.
alt.Chart(dados).mark_circle(color="blue").encode(
alt.X("Temperatura do ar - bulbo seco:Q", scale=alt.Scale(zero=False)),
alt.Y("Pressão Atmosférica ao nível da estação:Q", scale=alt.Scale(zero=False)),
alt.Fill("Temperatura mínima"),
alt.Size("Rajada Máxima de Vento"),
alt.OpacityValue(0.4),
alt.Order("Data"),
tooltip=[alt.Tooltip("Cidade"),
alt.Tooltip("Data")]
)
Apesar de estar carregado, com os tooltips tem como ver alguns padrões separados
Definimos color
com um valor padrão, o que acontece se colocar ele no scatterplot também?
___Small multiples___
alt.Chart(dados).mark_line().encode(
alt.X("Data:T"),
alt.Y("Umidade Relativa do Ar:Q"),
alt.Column("Cidade"),
tooltip=[alt.Tooltip("Umidade Relativa do Ar:Q"),
alt.Tooltip("Data:T")]
).properties(
width=100,
height=80
)
Trocamos o tamanho da saída para melhor visualizar os padrões, como fica sem essa definição?
O que acontece quando trocamos Column
por Row
?
Adicione tipos explícitos aos gráficos nesse notebook que ainda não tem. Identifique o mais adequado, e na dúvida teste e veja a diferença.
Crie um small multiples de scatterplots relacionando rajada do vento máxima por radiação global de cada cidade.
Crie um gráfico de linha usando Data e Temperatura do ponto de orvalho.
Usando as variáveis visuais alt.Y1
e alt.Y2
, crie um gráfico de área com temperatura mínima e temperatura máxima pelo tempo.
Crie um gráfico com dois atributos nominais na posição e um atributo numérico para cor. Escolha a marca visual mais apropriada.
Utilizando a base cars.json
(carregue pela url https://github.com/vega/vega-datasets/blob/master/data/cars.json
) crie os seguintes gráficos:
Selecione os atributos adequados para cada gráfico.