Através de interação podemos transformar um gráfico estático em ferramentas de exploração. Destaque de pontos de interesse, zoom para revelar padrões mais sutis, e ligar múltiplas visões para investigar relacionamentos de alta dimensionalidade são alguns modos de interação para visualizações.
No coração da interação existe a intuição da seleção: um meio de indicar quais elementos ou regiões são de interesse da análise. Em um ambiente tradicional nós já temos essas tarefas bem definidas, como clicar com o mouse, passar o cursor do mouse em um elemento ou escolher uma região.
Assim como as variáveis visuais, transformações e múltiplas visões, Altair também tem funções para seleção, em três aspectos principais:
Manipulação de eventos de entrada para selecionar pontos ou regiões de interesse, como eventos de passar o cursor do mouse, clicar, arrastar, scroll e toque.
Generalizar a partir da entrada para formar uma regra de seleção (ou predicado) que determina se um determinado registro de dados está ou não dentro da seleção.
Usando o predicado de seleção para configurar dinamicamente uma visualização conduzindo mapeamentos condicionais, transformações de filtro ou domínios de uma escala.
A seguir veremos alguns usos comuns.
import pandas as pd
import altair as alt
url = "https://raw.githubusercontent.com/tiagodavi70/vl-altair-tutorial/master/datasets/completo.csv"
df = pd.read_csv("https://raw.githubusercontent.com/tiagodavi70/vl-altair-tutorial/master/datasets/dados.csv")
Vamos começar vendo as formas de seleção pela mais simples, selecionar um único objeto.
A função alt.selection_single() cria uma seleção para um único valor do gráfico. Por padrão, essa seleção é feita com um clique do mouse. Para associar essa seleção ao gráfico vamos adicionar o método .add_selection().
Utilizando essa seleção, podemos criar um mapeamento condicional aos valores selecionados. Clique em um ponto para aplicar a seleção e ver as mudanças no gráfico. A seleção vazia contém todos os elementos.
Seguindo o exemplo abaixo, vamos trocar a cor dos pontos baseado em uma seleção.
selecao = alt.selection_single()
alt.Chart(df).mark_circle().add_selection(
selecao
).encode(
alt.X("Precipitação:Q"),
alt.Y("Umidade Relativa do Ar:Q"),
color=alt.condition(selecao,"Cidade:N",alt.value("grey")),
opacity=alt.condition(selecao,alt.value(.9),alt.value(.3))
)
Para mostrar os outros tipos de seleções vamos criar uma função para recriar o gráfico acima com uma seleção como parâmetro.
def plot(selecao):
return alt.Chart(df).mark_circle().add_selection(
selecao
).encode(
alt.X("Precipitação:Q"),
alt.Y("Umidade Relativa do Ar:Q"),
color=alt.condition(selecao,"Cidade:N",alt.value("grey")),
opacity=alt.condition(selecao,alt.value(.9),alt.value(.2))
).properties(
width=240,
height=180
)
Em Altair existem três tipos de seleção:
selection_single
- selceionar um elemento discreto, por padrão usando clique.selection_multi
- seleciona vários elementos discretos. O primeiro valor é seleciona com o clique e o resto com shift + clique
The first value is selected on mouse click and additional values toggled using shift-click.selection_interval
- seleção um intervalo contínuo de valores com o arrastar do mouse.Vamos mostrar os três tipos lado a lado.
alt.hconcat(
plot(alt.selection_single()).properties(title='Único (clique)'),
plot(alt.selection_multi()).properties(title='Multi (Shift-clique)'),
plot(alt.selection_interval()).properties(title='Intervalo (arrastar)')
)
E agora mudando os eventos padrões.
alt.hconcat(
plot(alt.selection_single(on='mouseover')).properties(title='Único (clique)'),
plot(alt.selection_multi(on='mouseover')).properties(title='Multi (Shift-clique)')
)
Uma busca dinâmica (Dynamic query) permite exploração rápida e reversível para isolar padrões. Tem como características:
O jeito mais comum de manipular essas buscas é através de elementos comuns da interface, como botões, sliders e menus. Em Altair podemos aplicar uma operação de bind
para uma seleção de um ou mais campos que desejamos buscar.
Vamos construir um scatterplot e uma opção para mostrar somente uma cidade de cada vez.
nomeCidades = list(df["Cidade"].unique())
selecionaCidade = alt.selection_single(
name='Selecao', # dando o nome 'Seleção'
fields=['Cidade'], # Seleção somente no campo cidade
init={'Cidade': nomeCidades[0]}, # a primeira cidade começa selecionada
bind=alt.binding_select(options=nomeCidades) # vincula um menu ao nome das cidades
)
alt.Chart(df).mark_circle().add_selection(
selecionaCidade
).encode(
alt.X("Umidade Relativa do Ar"),
alt.Y("Precipitação"),
color=alt.condition(selecionaCidade, "Cidade" ,alt.value("darkgrey")),
opacity=alt.condition(selecionaCidade, alt.value(.8),alt.value(.1))
)
Agora vamos fazer uma seleção múltipla.
selecionaCidade = alt.selection_single(
name='Selecao', # dando o nome 'Seleção'
fields=['Cidade', 'Mes'], # Seleção somente no campo cidade
init={
'Cidade': nomeCidades[0], # a primeira cidade começa selecionada
"Mes": 1 # o primeiro mês também
},
bind={
"Cidade":alt.binding_radio(options=nomeCidades), # vincula um menu ao nome das cidades
"Mes":alt.binding_select(options=[i for i in range(1,13)]) # vincula os meses
}
)
alt.Chart(df).mark_circle(size=100).add_selection(
selecionaCidade
).transform_calculate(
Mes="month(datum.Data) + 1" #criando um campo de mês, fica mais simples para usar na seleção
).encode(
alt.X("Umidade Relativa do Ar"),
alt.Y("Precipitação"),
tooltip=[alt.Tooltip("Cidade"),alt.Tooltip("month(Data)")],
color=alt.condition(selecionaCidade, "Cidade" ,alt.value("darkgrey")),
opacity=alt.condition(selecionaCidade, alt.value(.8),alt.value(.1))
)
A utilização de elementos de interface padrões conseguem mostrar os valores possíveis em uma busca, mas não a distribuição desses valores. Também não é tão intuitivo para fazer seleção de múltiplos valores ou intervalos. Em razão disso, nós podemos utilizar gráficos extras para tanto visualizar os dados quanto criar buscas dinâmicas. Criar essas visões coordenadas é chamado de brushing.
Vamos criar um exemplo em que filtramos o scatterplot usando o histograma da pressão atmosférica. Selecione uma área no histograma e vai refletir no scatterplot.
brush = alt.selection_interval(encodings=["x"]) # queremos só o intervalo no eixo X
histograma = alt.Chart(df).mark_bar(color="lightblue").add_selection(
brush
).encode(
alt.X("Pressão Atmosférica ao nível da estação:Q",bin=True),
alt.Y("count()")
).properties(width=400, height=50)
scatterplot = alt.Chart(df).mark_circle(size=100).encode(
alt.X("Umidade Relativa do Ar"),
alt.Y("Precipitação"),
tooltip=[alt.Tooltip("Cidade"),alt.Tooltip("month(Data)")],
color=alt.condition(brush, "Cidade" ,alt.value("darkgrey")),
opacity=alt.condition(brush, alt.value(.8),alt.value(.1))
)
histograma & scatterplot
Usando brushing também conseguimos usar os operadores de repetição para que todas as visualizações sirvam para visualizar e destacar itens.
colunas = ["Precipitação", "Temperatura mínima", "Umidade Relativa do Ar"]
linhas = colunas[::-1]
brush = alt.selection_interval(
resolve='global' # todas as seleções com brush passam a ser somente uma
)
alt.Chart(df).mark_circle().add_selection(
brush
).encode(
alt.X(alt.repeat("column"), type='quantitative', scale=alt.Scale(zero=False)),
alt.Y(alt.repeat("row"), type='quantitative', scale=alt.Scale(zero=False)),
tooltip=[alt.Tooltip("day(Data)"), alt.Tooltip("Cidade")],
color=alt.condition(brush, alt.value("steelblue"),alt.value("darkgrey")),
opacity=alt.condition(brush, alt.value(.8),alt.value(.1))
).properties(
width=120,
height=120
).repeat(
column=colunas,
row=linhas
)
Podemos mudar os domínios das escalas para modificar os limites dos dados. Para fazer isso de forma interativa, podemos vincular uma seleção de intervalo com os parâmetro bind='scales'
. Agora ao invés de um brush nós podemos arrastar o mouse para movimentar e dar zoom.
No gráfico abaixo, use Pan e Zoom para enteder melhor o amontoado no fundo do scatterplot
alt.Chart(df).mark_circle(size=100).add_selection(
alt.selection_interval(bind="scales")
).encode(
alt.X("Umidade Relativa do Ar"),
alt.Y("Precipitação", axis=alt.Axis(minExtent=30)), # estabilizar o título do eixo,
tooltip=[alt.Tooltip("Cidade"),alt.Tooltip("month(Data)")],
color="Cidade",
opacity=alt.value(.8)
)
Sendo um caso de uso comum, esse tipo de interação tem um atalho, adicionando interactive()
ao fim do gráfico produz o mesmo efeito.
alt.Chart(df).mark_circle(size=100).encode(
alt.X("Umidade Relativa do Ar"),
alt.Y("Precipitação", axis=alt.Axis(minExtent=30)), # estabilizar o título do eixo,
tooltip=[alt.Tooltip("Cidade"),alt.Tooltip("month(Data)")],
color="Cidade",
opacity=alt.value(.8)
).interactive()
Além de pan e zoom, podemos um gráfico para navegar em outro, no estilo visão geral + detalhes, com um gráfico mostrando tudo e outro que serve para navegar. Vamos ver abaixo um exemplo dessa interação, onde o gráfico de baixo é usado para detalhar o de cima quando se arrasta o mouse.
brush = alt.selection_interval(encodings=['x']);
base = alt.Chart().mark_area().encode(
alt.X("Data:T",title=None),
alt.Y("average(Temperatura do ar - bulbo seco):Q", scale=alt.Scale(zero=False), title="Temperatura")
)
alt.vconcat(
base.encode(alt.X('Data:T', title=None, scale=alt.Scale(domain=brush))),
base.add_selection(brush).properties(height=60),
data=df
)
Assim que encontramos pontos de interesse na visualização, é normal procurar mais detalhes. Detalhes sob demanda é fazer esse processo interativamente, mostrando informação sobre os valores selecionados. Já vimos os tooltips, mas eles só mostram informação de um ponto de cada vez. Agora vamos ver como mostrar de mais de um ao mesmo tempo.
passar = alt.selection_single(
on='mouseover', # seleção passando o cursor
nearest=True, # seleciona o ponto mais próximo
empty='none' # seleção vazia não retorna todos os pontos
)
clicar = alt.selection_multi(
empty='none' # seleção vazia não retorna todos os pontos
)
# scatter plo
scatterplot = alt.Chart().mark_circle().encode(
x='Umidade Relativa do Ar:Q',
y='Precipitação:Q'
)
# base para as camadas
base = scatterplot.transform_filter(
{'or': [passar, clicar]} # filtra os pontos de acordo com o critério
)
#adicionando em ordem:
# pontos do gráfico de dispersão, círculo por cima, e texto em duas camadas para não poluir os pontos
alt.layer(
scatterplot.add_selection(passar).add_selection(clicar),
base.mark_point(size=100, stroke='firebrick', strokeWidth=1),
base.mark_text(dx=4, dy=-8, align='right', stroke='white', strokeWidth=2).encode(text='Cidade:N'),
base.mark_text(dx=4, dy=-8, align='right').encode(text='Cidade:N'),
data=df
).properties(
width=600,
height=450
).interactive()
Agora podemos manipular o gráfico e destacar as cidades de cada medida com shift-clique
.
Os exemplos até aqui mostram o uso de interação com mapeamentos condicionais, mas podemos usar diretamente uma seleção como filtro. A sintaxe é a mesma que um filtro comum.
Vamos ver um exemplo com histogramas conectados.
linhas = ["Temperatura do ponto de orvalho", "Temperatura do ar - bulbo seco", "Precipitação"]
brush = alt.selection_interval(
encodings=['x'],
resolve='intersect'
)
histograma = alt.Chart().mark_bar().encode(
alt.X(alt.repeat('row'), type='quantitative',
bin=alt.Bin(maxbins=100),
axis=alt.Axis(format='f', titleAnchor='start') # formato flutuante, alinhado a esquerda
),
alt.Y('count():Q', title=None)
)
alt.layer(
histograma.add_selection(brush).encode(color=alt.value('lightgrey')),
histograma.transform_filter(brush)
).properties(
width=900,
height=100
).repeat(
row=linhas,
data=df
).configure_view(
stroke='transparent' # sem outline
)
Com isso podemos agregar vários filtros buscar informação de forma mais visual.
Faça uma seleção da ponta da temperatura do ponto de orvalho e do ínicio da precipitação e veja como a temperatura se comporta.
Crie um scatterplot onde o ponto que o mouse passar por cima fique maior.
Crie dois histogramas e reflita a seleção para filtrar um no outro.
Revisite 3 gráficos do notebook passado e adicione interação de pan e zoom.
Adicione seleção de interval em 2 gráficos do notebook passado em gráficos concatenados para criar um filtro cruzado.
Crie um scatterplot com histograma em que as barras no histograma refletem os pontos em tela do scatterplot. Adicione interação no scatterplot com movimentação e zoom.
Crie um gráfico de área com um atributo númerico pelas datas,e as cores mapeadas para cidade. Use bind='legend'
em uma seleção múltipla e clique nas cores da legenda para fazer seleção por cores.
Crie uma query dinâmica com um gráfico de linha para destacar pontos em um scatterplot.
Use alt.binding_range
para criar um slider com os meses em um scatterplot.
Crie uma seleção que faça zoom e pan em gráfico só na variável visual x
.
Use 3 queries dinâmicas em um único gráfico.
Crie um gráfico de barras com uma linha móvel entre as barras, que destaque o excesso da barra acima da linha em outra cor.