Digitalizando gráficos com Engauge Digitizer e Python

engauge_destaque

Você talvez já tenha passado pela seguinte situação: está escrevendo um trabalho ou fazendo uma pesquisa e encontra um figura que apresenta dados interessantes, mas está com baixa qualidade ou você gostaria de adicionar ou retirar elementos da figura. No entanto, os dados que deram origem não estão disponíveis. Como sair desta situação? É o que veremos no artigo de hoje.

Caso prefira ver em vídeo, clique no player abaixo. O artigo completo se encontra após o vídeo.

Figura original

Quando estava preparando o artigo sobre o caso do avião que ficou sem combustível em pleno voo eu achei um material do Departamento de Defesa americano sobre combustíveis de avião. Estava buscando tal material pois, conforme descrevi no artigo, a densidade do combustível varia com a temperatura e todo o problema ocorreu por um erro na unidade utilizada no valor de densidade. Originalmente eu planejava colocar um gráfico mostrando a variação de densidade com a temperatura, mas depois achei que não ia adicionar muita coisa ao enredo e deixei de lado. Mas agora vou utilizar o gráfico aqui para mostrar como podemos obter dados a partir de figuras e melhorar consideravelmente a apresentação dos mesmos.

Vamos começar vendo a figura original do material do Departamento de Defesa:

engauge_figura_original
Figura original do PDF. Qualidade muito baixa.

Convenhamos que a qualidade está muito ruim. O material original, que já era antigo pois o documento é de 1983, foi escaneado de forma não muito cuidadosa, sendo difícil enxergar as linhas de grade do gráfico. Obviamente que, sem tais linhas, a usabilidade do gráfico é reduzida. O documento não fornece uma tabela com valores. Caso tal tabela fosse fornecida, reconstruir o gráfico seria mais fácil, qualquer planilha eletrônica poderia ser utilizada para esse fim. Como não há tal tabela disponível, precisamos extrair os dados diretamente do gráfico.

Obviamente que uma das formas de se fazer isso seria manualmente. Ou seja, fazer a leitura de alguns pontos de cada linha do gráfico e reconstruir as linhas a partir de tais pontos. O primeiro problema dessa abordagem é que ela é lenta. O segundo, é ser mais suscetível a erros especialmente pela dificuldade de leitura em algumas regiões da figura. Por fim, tal abordagem até é razoável de ser feita nesse gráfico pois há claramente um perfil linear, sendo fácil extrapolar o comportamento geral de cada conjunto de dados a partir de poucos pontos. No entanto, caso o comportamento seguisse outro perfil, seriam necessários mais pontos para reconstrução com mais acurácia. Assim, uma abordagem utilizando software se mostra mais viável e com melhor custo-benefício.

O programa Engauge Digitizer

O Engauge Digitizer é um programa gratuito e de código-aberto que se propõe a recuperar dados a partir de arquivos de imagem. Os dados podem, então, ser salvos e utilizados de entrada em programas para construção de gráficos. A seguinte figura, retirada do site do projeto, mostra exatamente o processo:

figura_processo_engauge
Procedimento do Engauge Digitizer para extração de dados a partir de gráficos

O programa é multiplataforma, estando disponível nas lojas de aplicativos de boa parte das distribuições Linux e do Mac. Instaladores para tais sistemas e também para Windows estão disponíveis no repositório do projeto.

A utilização do programa é bem simples, bastando importar a figura e seguir alguns poucos passos. É mais fácil explicar utilizando um vídeo. Na parte superior deste artigo há o vídeo completo explicando as etapas de utilização.

Refazendo o gráfico com Python

Agora que temos um arquivo com os dados, podemos importá-lo em qualquer programa de planilha eletrônica ou utilizar alguma linguagem de programação. Como gosto de ter uma maior liberdade criativa, vou utilizar Python para reconstruir o gráfico. Abaixo segue um Jupyter Notebook com as etapas que segui. Caso queira baixar o Notebook, clique aqui para ir ao GitHub do Ciência Programada e baixar o arquivo.

Primeiro, vamos importar as bibliotecas que utilizaremos:

import numpy as np
from scipy.stats import linregress
import matplotlib.pyplot as plt

A função de cada biblioteca será mostrada no decorrer do arquivo.

Vamos salvar o caminho para o arquivo com os dados em uma variável. Isso facilita a modificação do código caso esse arquivo depois seja movido para uma outra pasta:

arquivo_dados = 'dados_combustivel.csv'

O numpy possui o método genfromtxt que permite extrair dados de arquivos de texto. O parâmetro delimiter permite especificar o delimitador utilizado no arquivo que, no caso, é vírgula por ser formato csv; o parâmetro names permite informar os nomes para as colunas de dados. O valor True significa que é para utilizar a linha logo acima dos dados numéricos do arquivo de origem:

dados = np.genfromtxt(arquivo_dados, delimiter=',', names=True)

Podemos visualizar as colunas disponíveis. Cada conjunto de dados pode ser obtido da variável a partir do nome da coluna com a sintaxe dados[nome_da_coluna]:

dados.dtype.names
('x', 'JP5', 'JetA', 'JP7', 'JetB', 'AvGas')

O Engauge Digitizer exportou pontos do nosso gráfico de origem. Podemos então plotar um primeiro rascunho de gráfico com cada conjunto de pontos usando o método scatter do Matplotlib para ter uma ideia do comportamento geral dos dados:

for name in dados.dtype.names[1:]:
    plt.scatter(dados['x'], dados[name], label=name)
plt.legend()
plt.show()

É bastante perceptível que cada conjunto de dados possui comportamento linear. Aqui entra em cena o método linregress importado da biblioteca SciPy.

Vamos dar uma olhada, por exemplo, nos dados de regressão linear do conjunto de dados referente ao combustível JetA:

linregress(dados['x'], dados['JetA'])
LinregressResult(slope=-0.7221033469533403, intercept=819.8747195466843, rvalue=-0.9999442604546929, pvalue=0.0, stderr=0.0005972008546052884)

Repare que o método retorna cinco valores que descrevem a regressão: inclinação da reta, intercepto, o valor de R (coeficiente de correlação), o valor de P e o desvio padrão. Uma descrição pormenorizada do significado de cada parâmetro pode ser obtida na documentação. Aqui para nós, o que interessa é verificar que o valor de R, ou mais especificamente o quadrado de R, fique o mais próximo de 1 e que o desvio padrão seja baixo para que realmente possamos considerar que os dados possuem um comportamento linear.

Vamos verificar para cada conjunto de dados com um loop:

for name in dados.dtype.names[1:]:
    regressao = linregress(dados['x'], dados[name])
    print(f"Regressão para {name}: R^2={regressao.rvalue**2:.4f} e stderr={regressao.stderr:.4f}")
Regressão para JP5: R^2=0.9998 e stderr=0.0007
Regressão para JetA: R^2=0.9999 e stderr=0.0006
Regressão para JP7: R^2=0.9998 e stderr=0.0008
Regressão para JetB: R^2=0.9981 e stderr=0.0026
Regressão para AvGas: R^2=0.9999 e stderr=0.0007

Logo, podemos ver que considerar cada conjunto linear é bastante razoável.

Podemos, então, plotar a reta resultante da regressão de cada conjunto e gerar nosso gráfico final. Vamos utilizar um dos estilos de gráfico pré-definidos no Matplotlib, que podem ser vistos aqui. E também colocar títulos nos eixos e no gráfico:

# faixa de valores de temperatura do gráfico
temperatura = np.arange(-40, 100, 10)

plt.style.use('ggplot')

for name in dados.dtype.names[1:]:
    regressao = linregress(dados['x'], dados[name])
    plt.plot(temperatura, regressao.slope * temperatura + regressao.intercept, label=name)
    plt.legend()
    
plt.xlabel('Temperatura / °C')
plt.ylabel('Densidade / kg/m³')
plt.title('Densidade de combustíveis de avião vs temperatura')
plt.show()

É um gráfico muito mais apresentável que o original, concorda? E muitas outras personalizações podem ser feitas a critério do usuário. Abaixo segue um gráfico mais elaborado, utilizando mais alguns recursos do Matplotlib:

# mudando a frequência de ticks (marcações) nos eixos
import matplotlib.ticker as plticker
ticks_densidade = plticker.MultipleLocator(base=20)
ticks_temperatura = plticker.MultipleLocator(base=10)

plt.style.use('ggplot')

fig, ax = plt.subplots(figsize=(6, 8), constrained_layout=True)

for name in dados.dtype.names[1:]:
    regressao = linregress(dados['x'], dados[name])
    ax.plot(temperatura, regressao.slope * temperatura + regressao.intercept, 
            label=name, linewidth=5)
    ax.legend(fontsize=14)
    
ax.xaxis.set_major_locator(ticks_temperatura)
ax.yaxis.set_major_locator(ticks_densidade)
ax.tick_params(labelsize=13)
ax.minorticks_on()
ax.grid(which='minor', linestyle=':', linewidth='1.0', color='white')

ax.set_xlabel('Temperatura / °C', fontsize=15)
ax.set_ylabel('Densidade / kg/m³', fontsize=15)
ax.set_title('Densidade de combustíveis de avião vs temperatura', fontsize=17)

plt.show()

Conclusão

O Engauge Digitizer é uma ferramenta muito útil, especialmente para aqueles que lidam com frequência com gráficos e nem sempre têm disponível os dados originais. Auxilia desde os que querem simplesmente um gráfico mais apresentável para um trabalho escolar até professores e pesquisadores ou mesmo organizações que lidam com grandes bancos de imagem. Seu poder aliado à uma ferramenta gráfica robusta como o Matplotlib permite transformar gráficos de má qualidade em imagens de alta resolução e muito mais informativas.

E aí, curtiu? Caso queira receber notificação quando novos artigos e vídeos sobre ciência e programação forem lançados, nos acompanhe nas redes sociais linkadas no cabeçalho e no rodapé da página. Comente o que achou abaixo e compartilhe com mais gente interessada em ciência.

Até a próxima.

Compartilhe:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima