domingo, 19 de julho de 2020

Tipos de dados com Pandas

Os tipos de dados utilizados na biblioteca pandas podem ser classificados como contínuos ou categóricos. Os dados contínuos são usados para representar dados numéricos como a distância entre dois pontos, peso ou altura. Já os dados categóricos são usados para representar um dado de uma quantidade finita. Um exemplo de objetos finitos são as cores que um monitor pode mostrar (16,7 milhões de cores). Os dados categóricos são usados para esse tipo de dado, representa um dado entre um número finito de possibilidades.
Na biblioteca Pandas existem alguns tipos de dados específicos. A seguir veja quais são esses tipos de dados:

  • int: Tipo inteiro da biblioteca NumPy. Esse tipo de dado não tem suporte a valores ausentes.
  • int64: Numero nulo inteiros do Pandas.
  • float: Numero de ponto flutuante da biblioteca NumPy. Esse tipo de dado suporta valores ausentes.
  • object: Com esse tipo de dados você trabalhar com sequencias de caracteres. Esses caracteres podem ser números ou letras.
  • category: Tipo categórico da biblioteca Pandas.
  • bool: Tipo booleano da biblioteca NumPy. Esse tipo de dado não suporta dados ausentes.
  • boolean: Tipo booleano que suporta valor nulo.
  • datetime64: Tipos data da biblioteca NumPy. Esse tipo aceita valores ausentes.

Uma coisa muito importante de saber é o tipo de dados usados num dataframe. Esse conhecimento é importante porque é o tipo de dado que determina quais operações são possíveis. Após a criação do dataframe podemos saber quais são os tipos de dados armazenados em cada coluna.

Como verificar o tipo de dados de um dataframe

Para verificar os tipos de dados de um dataframe podemos fazer uso do atributo dtypes, do método value_counts() ou do método info().
Com o atributo dtypes você vai receber uma saída com duas colunas. A primeira coluna contem os valores do primeiro elemento de cada coluna. E a segunda coluna o tipo de dados que a coluna contém.

>>> import pandas as pd
>>> filmes = pd.read_csv('filmes.csv', encoding='latin-1')
>>> filmes.dtypes
Codigo da obra                  int64
Titulo Original                object
Titulo no Brasil               object
Ano de producão               float64
Diretor                        object
Razão Social do Requerente     object
CNPJ Requerente                object
Data de exibicão               object
dtype: object

A saída do método value_counts() são duas colunas. A primeira coluna se refere ao tipo de dados e a segunda a quantidade de colunas que contém dados desse tipo.

>>> filmes.dtypes.value_counts()
object     6
float64    1
int64      1
dtype: int64

Já o método info() nos fornece algumas informações sobre o dataframe como números de colunas, de linhas e a memória que esse dataframe está utilizando.

>>> filmes.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9262 entries, 0 to 9261
Data columns (total 8 columns):
 #   Column                      Non-Null Count  Dtype
---  ------                      --------------  -----
 0   Codigo da obra              9262 non-null   int64
 1   Titulo Original             9262 non-null   object
 2   Titulo no Brasil            9262 non-null   object
 3   Ano de producão             9259 non-null   float64
 4   Diretor                     9262 non-null   object
 5   Razão Social do Requerente  9262 non-null   object
 6   CNPJ Requerente             9262 non-null   object
 7   Data de exibicão            9262 non-null   object
dtypes: float64(1), int64(1), object(6)
memory usage: 361.9+ KB

O tipo de dado object, normalmente, é uma string. Caso esteja faltando algum elemento de uma coluna será atribuído o valor NaN (do tipo float) para o elemento.

Atributos do dataframe com Pandas

O index, colunas e dados podem ser acessados a partir de um dataframe. Isso é útil quando você precisar acessar um valor especifica e não o dataframe inteiro. Se estamos trabalhando com uma matriz numérica deixamos esses dados numa matriz NumPy, mas se apenas algumas linhas ou colunas são numéricas guardamos esses dados num dataframe Pandas. Com dataframes podemos trabalhar com matrizes que não são numéricas mais possuem linhas ou colunas heterogêneas de dados numéricos.
No exemplo abaixo vamos criar variáveis para visualizar os dados do dataframe individualmente, como o index, as colunas e os dados.

Como fazer isso...

Nesse exemplo vamos usar os atributos do dataframe para colocar cada dado na sua própria variável e depois mostrar o conteúdo de cada uma delas.

>>> import pandas as pd
>>> filmes = pd.read_csv('filmes.csv', encoding='latin-1')
>>> index = filmes.index
>>> colunas = filmes.columns
>>> dados = filmes.to_numpy()
>>> index
RangeIndex(start=0, stop=9262, step=1)
>>> colunas
Index(['Codigo da obra', 'Titulo Original', 'Titulo no Brasil',
       'Ano de producão', 'Diretor', 'Razão Social do Requerente',
       'CNPJ Requerente', 'Data de exibicão'],
      dtype='object')
>>> dados
array([[15639, 'PREÇO DA PAZ O', 'PREÇO DA PAZ O', ...,
        'M A PRODUÇÕES ARTISTICAS E CULTURAIS LTDA ME',
        '00.568.159/0001-07', '06/jul/12'],
       [7603, 'CARTOMANTEA', 'CARTOMANTEA', ...,
        'TAG CULTURAL DISTRIBUIDORA DE FILMES LTDA',
        '03.599.148/0001-82', '13/jul/04'],
       [26453, 'BLACK & WHITE VOL. 9', 'BLACK & WHITE VOL. 9', ...,
        'FALLMS DISTRIBUIÇÃO DE FITAS LTDA', '02.341.697/0001-90',
        '26/09/2007'],
       ...,
       [19002684, 'MILITARY WIVES', 'MILITARY WIVES', ...,
        'ANTONIO FERNANDES FILMES LTDA', '02.668.665/0001-01',
        '14/02/2020'],
       [609762, 'ROBERTO CARLOS EM JERUSALÉM',
        'ROBERTO CARLOS EM JERUSALÉM', ...,
        'H2O DISTRIBUIDORA DE FILMES S/A', '15.372.472/0001-42',
        '20/12/2019'],
       [19005137, 'O POÇO', 'O POÇO', ..., 'ANDRE BORELLI MARTINS',
        '408.504.318-83', '18/10/2019']], dtype=object)

Como funciona…

As colunas e os índices são quase a mesma coisa, o que muda é o sentido de cada um (ou eixo). É comum que o index seja chamado de “eixo 0” e a coluna de “eixo 1”.
Por padrão a biblioteca Pandas usa o tipo de dado RangeIndex, mais existem vários tipos de dados que podem ser usados no seu lugar. Utilizar o tipo RangeIndex tem a vantagem de que apenas os valores necessários são carregados na memória, isso ajuda a economizar memória. Esse tipo de dado consiste no valor de inicio, o valor de parada e o incremento.

Tem mais...

O índices e as colunas são implementados como tabelas de hash. Com isso é possível fazer um alinhamento e uma seleção mais rápida. Os objetos index (índices e colunas) são parecidos com os conjuntos do Python, a diferença é que eles são ordenados e aceitam entradas duplicadas.

Introdução ao Pandas

Nesse primeiro post vamos aprender alguns conceitos importantes quando trabalhamos com pandas: estruturas de dados, séries e dataframe. Uma coisa que deve ficar bem clara é a diferença entre uma serie e um dataframe.
A biblioteca pandas é utilizada para trabalhar com dados estruturados. Arquivos CSV, planilhas do Excel e bancos de dados são alguns exemplos de dados estruturados. Dados não estruturados é todo tipo de dado que não tem uma estrutura lógica. Se estiver trabalhando com dados estruturados a biblioteca pandas será útil em algum momento e, às vezes, necessária.
Em primeiro lugar você precisa saber que um dataframa é um conjunto de dados com duas dimensões (bidimensional) e uma serie é um conjunto de dados unidimensional. Nos próximos posts vamos aprender como ler os dados de apenas uma coluna de um dataframe e retornar uma serie.
O que torna a biblioteca Pandas diferente das outras é o index. O index é apenas um número que funciona como rótulo de um dado. Nos próximos posts ficará claro como utilizar o index torna a biblioteca pandas única. Isso será quando utilizamos índices como rótulos dos elementos de uma série.

Dataframe Pandas

Dataframe pode ser encarado como uma matriz bidimensional. Essa matriz é dividida em linhas e colunas. Um dataframe tem três componentes importantes: o index, as colunas e os dados. Você deve aprender a utilizar cada um desses elementos para dominar a biblioteca Pandas.
No exemplo abaixo vamos ler os dados do dataset filmes.csv (você pode baixa o arquivo aqui) e guardar esses dados num dataframe da biblioteca Pandas. Com isso conseguimos um diagrama rotulado dos principais componentes do dataset.

>>> import pandas as pd
>>> filmes = pd.read_csv('filmes.csv', encoding='latin-1')
>>> filmes
      Codigo da obra  ... Data de exibicão
0              15639  ...        06/jul/12
1               7603  ...        13/jul/04
2              26453  ...       26/09/2007
3              17284  ...       16/12/2002
4               4806  ...        15/jan/03
...              ...  ...              ...
9257        16001794  ...        09/jul/20
9258        15000966  ...        25/nov/19
9259        19002684  ...       14/02/2020
9260          609762  ...       20/12/2019
9261        19005137  ...       18/10/2019
    
[9262 rows x 8 columns]

Como funciona...

Primeiro a biblioteca Pandas ler os dados do disco e aloca esses dados na memoria como um dataframe, utilizando o método read_csv(). Utilizamos o index para fazer referencia a uma linha e coluna para fazer referencia a uma coluna.
Com o index e a coluna podemos ler o valor de um elemento isolado. Nos próximos post veremos mais como fazer isso. Ao combinar várias séries, os indexes são alinhados antes que qualquer coisa aconteça.
É comum, nas documentações sobre dataframe, se referir ao índice como eixo 0 e as colunas como eixo 1. No exemplo acima é possível notar linhas e colunas com sequências de três pontos. Isso indica que, pelo menos, uma linha ou coluna não foram mostradas. Outro acontecimento comum é o NaN. Isso indica que o elemento está vazio.

sábado, 18 de julho de 2020

Exceções e tratamento de exceções com Delphi

Introdução

Desde a primeira versão do Delphi ele já trás o tratamento de exceções. Mesmo já fazendo muito tempo que essa funcionalidade acompanha o Delphi, isso não impediu que o tratamento de exceções fosse usado de maneira equivocada e errada até hoje.
Nesse post veremos como o tratamento de exceções deve ser feito de maneira correta. Para começar vou mostrar alguns exemplos de como NÃO usar esse recurso. Tendo esses exemplos em mente vamos discutir maneiras mais apropriadas de usar o tratamento de exceções. Uma coisa que a maioria dos programadores não percebe é que utilizar um tratamento de exceções de maneira errada pode causar mais problemas do que resolver. Utilizando esse recurso da linguagem de maneira correta o seu código fica mais fácil de ler e manter.

Tratamento de exceções estruturadas

A exceção é um recurso das linguagens de programação que permite ao programador criar um bloco de código que será executado se algum problema, não previsto acontecer. Com esse recurso o programador pode responder de maneira adequada ao erro quando ele acontece ou, até mesmo tratar esse erro.
A maioria dos programas segue uma sequencia de acontecimentos. Assim um determinado número de funções deve ser executado, corretamente, antes das funções seguintes. Um exemplo disso é uma função que excluí um registro de um banco de dados. Para que essa função seja executada corretamente é preciso que o usuário já esteja conectado ao banco de dados. Como as interfaces dos programas só disponibilizam a opção de excluir um registro depois que o usuário se conecta ao banco de dados, o programador pode deduzir que o usuário estará conectado ao banco de dados. Nessa hipótese o que acontece se o banco de dados apresentar uma falha e desconectar do usuário?
Uma solução para esse problema é verificar a conexão a cada chamada da função. Mas isso pode levar a duplicação de código, ou o programador pode simplesmente esquecer-se de fazer uma verificação.
Todos esses problemas podem ser resolvidos com o tratamento de exceções estruturadas. Uma exceção permite que a aplicação não pare de funcionar por apenas um erro. Além disso, é possível resolver o problema dentro de uma exceção.

Como não usar exceções

Uma boa parte do trabalho de um programador é em projetos existentes que estão com problemas (Isso por cauda de um projeto mal feito) e refazendo o trabalho que não foi bem feito na primeira vez. Um erro bem comum é o mau uso das exceções. Começaremos analisando os exemplos "Não faça", para ter uma noção de porque essas técnicas não são uma boa ideia.

Não esqueça as exceções

O uso mais comum e presente das exceções é o de esquecer o tratamento das exceções. Não é muito difícil encontrar códigos escritos como esse:

try
    AlgumaRotinaQueAsVezesCausaUmaViolaçãoDeAcesso
except
end;

Nesse exemplo é possível ver que o programador está evitando qualquer exceção que possa aparecer. Não é raro uma rotina no bloco try ocasionar num erro que não é encontrado com facilidade e, em vez de seguir o caminho difícil (e correto) de encontrar o erro, o programador segue o caminho mais fácil e executa uma exceção. O que alguns desenvolvedores desejam com isso é que o usuário nunca veja uma mensagem de erro. Se esse é o seu objetivo você ainda pode conseguir isso sem esconder os erros do resto do código.
Com esse exemplo todas as exceções serão suprimidas - exceções de bando de dados, exceções de falta de memória, falhas de hardware, qualquer um desses. Desse modo o seu programa pode retornar com êxito quando, na verdade, deveria ter retornado com erro. Assim fica mais difícil encontrar um erro. É melhor tratar o erro da forma correta do que ter um erro silencioso que pode resultar no desmoronamento de todo o seu sistema.
Uma exceção pode ser aceitável quando você estiver trabalhando com módulos, desse modo é possível impedir que uma exceção afete todo o seu sistema. Um exemplo disso é com DLLs, nesse cenário não deixe que nenhuma exceção escape. Com uma exceção vazia no final de uma DLL esse problema seria resolvido. Mas, se esse não for o caso, não deixe blocos de exceções vazias. E mesmo nesses casos, você deve ser informado se algum erro ocorrer ou registrar esse erro. Ficar sem saber as informações dificulta a busca pela solução do problema. É possível que apenas "pulando" esse erro o seu cliente nunca descubra que existe um problema, mas também é possível que ele descubra, e sem as informações certas você não sabe o que aconteceu e muito menos como consertar.

Não intercepte exceções de forma genérica

Não é muito difícil encontrar códigos escritos dessa maneira:

try
    AlgoQueTalvezCauseProblemas
except
   on E: Exception do
   begin
      MessageDlg(E.Message, mtWarning, [mbOK], 0);
   end;
end;

O que esse código faz é relatar um problema que de um jeito ou de outro seria relatado. Mas ele faz mais do isso, ele interrompe a exceção. A exceção será tratada no escopo local, dessa maneira ela não vai escapar do escopo atual. E ainda vai capturar todas as exceções, inclusive até aquelas que você pode não querer.
Esse código é um pouco melhor do que "pular" as exceções (só um pouco). Você pode considerar usar esse código quando chamar uma rotina que não vai lidar com nenhuma exceção ou quando ela for lidar com uma exceção especifica. Um exemplo disso é o evento OnReconcileError do TClientDataset que passa uma exceção. Quando um processo em lote do Clientdataset lança uma exceção isso pode interromper o loop. Desse modo é possível que você queira capturar todas as exceções de forma genérica.

Não procure exceções

Em termos de processamento as exceções são caras para criar e manipular, sendo assim você não deve criá-las sem um proposito. E não criá-las com o proposito, apenas, de verificar a ocorrência de um erro.
Você pode estar tentando fazer algo parecido com esse exemplo:

function StringIsInteger(str: string): Boolean;
var
    Tmp: integer;
begin
   Result := True;
   try
     Tmp := StrToInt(str);
   except
      Result := False;
   end;
end;

Com esse código você consegue o que quer, mas é provável que gere muitas exceções. E isso pode trazer problemas para o desempenho da aplicação, especialmente quando a probabilidade do resultado for False for alto.
Deixando de lado o desempenho, esse exemplo faz mau uso do tratamento de exceções. Claramente a função foi projetada para aceitar parâmetros não inteiros. Por isso uma exceção não deve ser usada nesse caso. Uma maneira mais inteligente de resolver esse problema seria usar TryStrToInt do SysUtils.

Não use o tratamento de exceções como um sistema de sinalização genérico

type
TExceçãoDeComportamentoNormal = class(Exception);
    ...
begin
   CoisasNormaisESemErros;
   raise TExceçãoDeComportamentoNormal.Create('Algo perfeitamente normal' + 'e esperado aconteceu');
end;

Muitos desenvolvedores fazem uso do exemplo acima para sinalizar que ocorreu tudo bem na rotina. Você também pode se sentir tentado a fazer a mesma coisa, mas lembre-se que as exceções devem ser usadas para controlar o fluxo do programa em casos específicos e passar informações. Utilizar esse recurso para enviar uma mensagem de "que tudo ocorreu bem" pode ter consequências inesperadas.

Como usar exceções de maneira apropriada

Agora que você sabe algumas maneiras de como não usar exceções, aqui estão algumas dicas para usar corretamente as exceções do Delphi.

Use exceções para que seu código seja executado sem a Interrupção de tratamento de erros

Um dos objetivos das exceções é separar o código do seu programa do código de tratamento de algum erro eventual que possa acontecer na execução do seu programa. Desse modo é possível escrever o seu código como se nada fosse dar errado e, depois, separar o código com try e except para tratar algum problema desse código. Assim o seu código é executado com eficiência porque ele não está a todo o momento procurando por parâmetros errados para garantir que está tudo certo, para só então fazer alguma coisa com esses parâmetros.
Uma boa pratica é separar o código das exceções com TApplication. Esse objeto possui um evento chamado OnException que serve perfeitamente para esse tipo de situação. Com esse evento você pode lidar com todas as exceções que não forem tratadas pelo seu programa. OnException permite você registrar as exceções ou aplicar um tratamento para cada tipo especifico de exceção.

Os desenvolvedores devem capturar as exceções

Como veremos abaixo, as bibliotecas e os componentes devem ser a principal fonte de exceções. Esses são os locais onde a maioria das exceções é criada. Escrevendo aplicativos a necessidade de criar exceções é baixa. Os desenvolvedores devem lidar com as exceções geradas pelos componentes e pelas bibliotecas.

Capture apenas exceções especificas

Como já falamos acima, nunca "pule" uma exceção. A coisa mais produtiva a se fazer é capturar apenas exceções especificas do seu código. Por exemplo, se você esta fazendo muitas conversões capture EConvertError. Ou EMathError, se você estiver utilizando muita matemática.
Como mencionado acima, existem programadores (ou gerente de projeto) que não querem que o usuário veja uma mensagem de erro. Isso não é um problema em se, o que causa o problema é "pular" exceções. Uma maneira de não deixar o usuário ver uma mensagem de erro e não "pular" essa exceção é capturar a exceção especifica que o usuário está vendo. Veja um exemplo:

try
   CódigoQueVaiLançarEConvertError;
except
   on E: EConvertError do
   begin
      // Trate o erro EConvertError aqui
   end;
end;

Com esse código apenas uma exceção especifica será capturada e não todas. É uma coisa simples mais é melhor do que pular todas as exceções.
A maioria das exceções tem um código de erro, você pode capturar apenas erros com o código que lhe interessa e permitir que outros erros apareçam. Veja um exemplo de como isso pode ser feito:

try
   LançaOErroEConvertError;
except
   on E: EIBError do
   begin
      if E.ErrorCode = ErroQueQueroPegar then
         begin
            // Trate desse erro específico
         end else
         begin
            raise; // Lança uma exceção se não for a única
         end;
   end;
end;

Um motivo para capturar as exceções no nível hierárquico mais baixo é que pode haver futuras exceções que descendem da classe Exception. Veja um exemplo:

try
   AlgumDataset.Open
except
   on E: EDatabaseError do
   begin
      // Trate a exceção
   end;
end;

e deposi ceclarar:

type
   ENxEstranhoDatabaseError = class(EDatabaseError)

Essa exceção estranha será capturada pelo código, talvez isso não seja oque você queira. Certamente não é possível impedir que isso aconteça em cada acontecimento. Mas a frequência que acontece pode diminuir capturando exceções na parte mais inferior do código.
Conclusão: intercepte as exceções o mais longe possível na hierarquia de classes e intercepte apenas as exceções que você planeja manipular.

Componentes e bibliotecas lançam exceções

As exceções não aparecem do nada. A sua grande maioria é criada dentro do código. (Algumas poucas podem ser criadas fora do código Delphi) Mesmo assim é bom que você faça as suas próprias exceções.
Uma boa prática é criar exceções para cada erro dentro das suas bibliotecas e componentes. Assim outros desenvolvedores podem capturar essas exceções especificas.
Você deve escrever os seus códigos para que eles retornem apenas dois resultados: o resultado esperado ou uma exceção.

Deixe que os usuários vejam mensagens de exceção

Quando você esconde todas as exceções do usuário o que pode acontecer é o programa avançar sem ter concluído as tarefas e deixando dados corrompidos para trás. E o pior é que o usuário acha que tudo ocorreu bem.
Muitos especialistas em interface observaram que os usuários têm medo das caixas de diálogo. Um dos motivos é que elas não fornecem algo útil para o usuário fazer, o que a maioria faz é tipo isso: mostrar uma mensagem tipo "F@#$%... Você acaba de perder o trabalho de 2 horas" (Com certeza as mensagens não chegam nem perto disso, mas é o quê os usuários entendem. E estão errados!?) e um botão de OK. Uma coisa a se fazer é passar informações realmente uteis, para que o erro possa ser corrigido, isso de uma maneira amigável.

Boas mensagens para as exceções

Quando criar uma mensagem de erro sinta-se livre para detalhar o tipo de erro. Use mensagens curtas e diretas para informar o que acabou de ocorrer.
Não faça algo do tipo:

type
   EAlgumaExcecao = class(Exception);

procedure CausaUmaExcecao;
begin
   raise EAlgumaExcecao.Create('Mensagem qualquer');
end;

Quando você pode fazer algo parecido com isso:

type
   EAlgumaExcecao = class(Exception);
procedure CausaUmaExcecao;
begin
   raise EAlgumaExcecao.Create('Um erro ocorreu, e foi isso que aconteceu....');
end;

Ao escrever uma mensagem de erro descreva o que aconteceu de forma descritiva e completa. Nessa mensagem talvez você queira informar o nome do procedimento ou do objeto.

Conclusão

É muito mais fácil usar o tratamento de exceções de maneira incorreta. Com algumas linhas de código fazer um erro "desaparecer" é muito tentador. Mas, infelizmente, usar as exceções desse jeito pode causar mais problemas ao invés de fazer os existentes desaparecer. O uso correto do tratamento de exceções facilita a leitura do código e a sua manutenção. Use as exceções corretamente para criar um código limpo e organizado.

Introdução a gráficos 3D com Matplotlib

Antes de começarmos a criar gráficos 3D com a biblioteca Matplotlib precisamos habilitar o módulo tollkit da biblioteca. Para habilitar esse módulo precisamos importar a biblioteca mplot3d. Não se preocupe se você não instalou essa biblioteca, por padrão ela já vem com a biblioteca matplotlib. Apenas uma ressalva quanto a isso: verifique se a versão do Matplotlib é igual ou superior à versão 1.0.
Quando o módulo já tiver sido importado, é fácil criar um gráfico 3D apenas passando o valor '3d' para o parâmetro projection de qualquer método para a criação de eixos do matplotlib. Veja um exemplo abaixo:

Exemplo Python

from mpl_toolkits import mplot3d

import numpy as np
import matplotlib.pyplot as plt
    
fig = plt.figure()
eixos = plt.axes(projection="3d")
    
plt.show()

Com os nossos eixos criados já podemos começar a fazer desenhos nesses eixos. Os métodos para criar gráficos em três dimensões são muito similares aos métodos para criar gráficos em 2D, a diferença é a terminação do método que termina com 3d. Outra diferença é o número de argumentos, ao invés de passar apenas dois argumentos passaremos três, um para cada eixo. Os outros parâmetros permanecem os mesmo, como os parâmetros para alterar a cor e a linha dos gráficos. Veja um exemplo abaixo:

Exemplo Python

from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
    
fig = plt.figure()
eixos = plt.axes(projection="3d")
    
z_line = np.linspace(0, 15, 1000)
x_line = np.cos(z_line)
y_line = np.sin(z_line)
eixos.plot3D(x_line, y_line, z_line, 'gray')
    
pontos_z = 15 * np.random.random(100)
pontos_x = np.cos(pontos_z) + 0.1 * np.random.randn(100)
pontos_y = np.sin(pontos_z) + 0.1 * np.random.randn(100)
eixos.scatter3D(pontos_x, pontos_y, pontos_z, c=pontos_z, cmap='viridis');
    
plt.show()

Assim como os gráficos 2D, podemos mover o gráfico 3D, dar zoom ou mudar o ângulo de visão.

Gráficos de superfície

Os gráficos de superfície são usados para visualizar um conjunto de três variáveis num cenário 3D. Com esse tipo de gráfico é possível visualizar como o valor de um eixo em relação aos outros dois. Veja um exemplo abaixo:

Exemplo Python

from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
    
fig = plt.figure()
ax = plt.axes(projection="3d")
    
ax = plt.axes(projection='3d')
    
def funcao_z(x, y):
    return np.sin(np.sqrt(x ** 2 + y ** 2))
    
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
    
X, Y = np.meshgrid(x, y)
Z = funcao_z(X, Y)
    
ax.plot_wireframe(X, Y, Z, color='green')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
    
ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
                    cmap='viridis', edgecolor='none')
ax.set_title('Gráfico de superfícies');
    
plt.show()

Gráficos de fluxo com Matplotlib

Um gráfico de fluxo é um tipo de gráfico 2D usado para mostrar fluxo de fluido e gradientes de campo 2D.
O método básico para criar um gráfico de fluxo no Matplotlib é:
ax.streamplot(x_grid,y_grid,x_vec,y_vec, density=espacamento)
Onde x_grid e y_grid são matrizes dos pontos x, y. As matrizes x_vec e y_vec denotam a velocidade do fluxo em cada ponto da grade. O argumento do parâmetro density=espacamento especifica a proximidade das linhas de fluxo.

Um gráfico de fluxo simples

Vamos começar com um gráfico de fluxo que contém linhas de fluxo em uma grade 10 x 10. Todas as linhas de fluxo no gráfico são paralelas e apontam para a direita.
A seção de código a seguir cria um gráfico de fluxo que contém linhas paralelas horizontais apontando para a direita.

Exemplo Python

import numpy as np
import matplotlib.pyplot as plt
# Se estiver usando o jupyter notebook, use:
# %matplotlib inline
    
x = np.arange(0,10)
y = np.arange(0,10)
    
X, Y = np.meshgrid(x,y)
u = np.ones((10,10)) # x-component to the right
v = np.zeros((10,10)) # y-component zero
    
fig, ax = plt.subplots()
    
ax.streamplot(X,Y,u,v, density = 0.5)
ax.set_title('Gráfico de fluxo com linhas pararelas')
plt.show()

O gráfico contém linhas de fluxo paralelas, todas apontando para a direita.

Gráfico de fluxo de um campo

Podemos construir um gráfico de fluxo que mostra linhas de campo com base em um campo vetorial 2D definido.

Exemplo Python

import numpy as np
import matplotlib.pyplot as plt
# Se estiver usando o jupyter notebook, use:
# %matplotlib inline
    
x = np.arange(0,2.2,0.1)
y = np.arange(0,2.2,0.1)
    
X, Y = np.meshgrid(x, y)
u = np.cos(X)*Y
v = np.sin(y)*Y
    
fig, ax = plt.subplots()
    
ax.streamplot(X,Y,u,v, density = 1)
ax.axis([0.5,2.1,0,2])
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
ax.set_title('Gráfico de fluxo com linhas de campo')
    
plt.show()

Gráfico de setas com Matplotlib

Gráfico de aljavas

Um gráfico de aljavas é um tipo de gráfico 2D que mostra linhas vetoriais como setas. Os gráficos com setas são úteis na Engenharia Elétrica para visualizar o potencial elétrico e útil na Engenharia Mecânica para mostrar gradientes de tensão.

» Gráfico de aljavas com uma seta

Primeiro, criaremos um gráfico de aljavas simples que contém uma seta para demonstrar como o método quiver() da biblioteca Matplotlib funciona. O método quiver() usa quatro argumentos posicionais:

Exemplo Python

ax.quiver(x_pos, y_pos, x_direct, y_direct)

x_pos e y_pos são as posições iniciais da seta e x_direct, y_direct são as direções da seta (pra onde a seta aponta).
Nosso primeiro gráfico contém uma seta no ponto inicial x_pos = 0, y_pos = 0. A direção da flecha está apontando para cima e para a direita x_direct = 1, y_direct = 1.
O exemplo de código a baixo cria um gráfico de aljavas com apenas uma seta.

Exemplo Python

import numpy as np
import matplotlib.pyplot as plt
# se estiver usando o Jypyter notebook, use:
# %matplotlib inline
    
fig, ax = plt.subplots()
    
x_pos = 0
y_pos = 0
x_direct = 1
y_direct = 1
    
ax.quiver(x_pos, y_pos, x_direct, y_direct)
ax.set_title('Gráfico de aljavas com uma seta')
    
plt.show()

Executando o código acima o seu gráfico deve esta assim:

Nesse exemplo o gráfico de aljava contém uma seta. A seta começa no ponto 0, 0 e termina no ponto 1, 1.

» Gráfico de aljavas com duas setas

Agora vamos adicionar uma segunda seta ao gráfico de aljava passando em dois pontos de partida e duas direções de seta.
Manteremos nossa posição inicial da seta original na origem 0,0 e apontando para cima e para a direita (na direção 1,1). Definiremos uma segunda seta com uma posição inicial de -0,5,0,5, que aponta diretamente para baixo (na direção 0, -1).
Um argumento de palavra-chave adicional para adicionar o método quiver() é scale=5. Incluir o parâmetro scale=5 dimensiona o comprimento das setas, para que as setas pareçam mais longas e apareçam melhor no gráfico de aljava.
Para ver o início e o fim de ambas as setas, definiremos os limites do eixo entre -1,5 e 1,5 usando o método ax.axis() e passaremos uma lista dos limites do eixo no formato [xmin, xmax, ymin, ymax] .
Executando o exemplo abaixo, podemos ver duas setas. Uma seta aponta para o canto superior direito e a outra seta aponta para baixo.

Exemplo Python

import numpy as np
import matplotlib.pyplot as plt
# se estiver usando o Jypyter notebook, use:
# %matplotlib inline
    
fig, ax = plt.subplots()
    
x_pos = [0, 0]
y_pos = [0, 0]
x_direct = [1, 0]
y_direct = [1, -1]
    
ax.quiver(x_pos, y_pos, x_direct, y_direct, scale=5)
ax.axis([-1.5, 1.5, -1.5, 1.5])
    
plt.show()

Nesse exemplo podemos ver um gráfico com duas setas. Ambas as setas começam no ponto 0,0. Uma seta aponta para o canto superior direito, a outra seta aponta para baixo.

» Gráfico de aljavas usando uma grade de malha

Um gráfico de aljava com duas setas é um bom começo, mas é entediante e repetitivo adicionar as flechas de aljava uma a uma. Para criar uma superfície 2D completa de setas, utilizaremos a função meshgrid() da biblioteca NumPy.
Primeiro, precisamos criar um conjunto de matrizes que denotem as posições iniciais x e y de cada seta no gráfico. As matrizes da posição inicial da seta serão denominadas X e Y.
Podemos usar as posições iniciais da seta x, y para definir os componentes x e y de cada direção da seta. Chamaremos a direção da seta u e v. Para esse gráfico, definiremos a direção da seta com base no ponto inicial da seta da aljava usando as equações abaixo.

Xdireção = COS(Xposição_inicial)
Ydireção = SIN(Yposição_inicial)

A seção de código abaixo cria as matrizes das posições X e Y usando a função np.meshgrid() da biblioteca NumPy.

Exemplo Python

import numpy as np
import matplotlib.pyplot as plt
# se estiver usando o Jypyter notebook, use:
# %matplotlib inline
    
x = np.arange(0,2.2,0.2)
y = np.arange(0,2.2,0.2)
    
X, Y = np.meshgrid(x, y)
u = np.cos(X) * Y
v = np.sin(y) * Y

Em seguida, podemos construir o gráfico de aljava usando o método quiver() do Matplotlib. Lembre-se de que o método quiver() aceita quatro argumentos posicionais:

ax.quiver(x_pos, y_pos, x_direct, y_direct)

Nesse gráfico de aljava, x_pos e y_pos são matrizes 2D que contêm as posições iniciais das setas e x_direct, y_direct são matrizes 2D que contêm as direções das setas.
Os comandos ax.xaxis.set_ticks([]) e ax.yaxis.set_ticks([]) remove as marcas de seleção do eixo e ax.set_aspect('equal') define a proporção da plotagem para 1:1.

Exemplo Python

fig, ax = plt.subplots(figsize=(7,7))
ax.quiver(X,Y,u,v)
    
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
ax.axis([-0.2, 2.3, -0.2, 2.3])
ax.set_aspect('equal')
    
plt.show()

Agora vamos construir outro gráfico de aljava em que os componentes Î e ĵ (a direção) das setas de força, ⃗F dependem do ponto inicial da seta x, y de acordo com a função:

Novamente, usaremos a função meshgrid() do NumPy para criar as matrizes da posição inicial da seta e aplicar nossa função ⃗F às matrizes do ponto inicial da seta X e Y.

Exemplo Python

import numpy as np
import matplotlib.pyplot as plt
# se estiver usando o Jupyter notebook, use:
# %matplotlib inline
    
x = np.arange(-1,1,0.1)
y = np.arange(-1,1,0.1)
    
X, Y = np.meshgrid(x, y)
u = np.cos(X)*Y
v = np.sin(Y)*Y
    
X,Y = np.meshgrid(x,y)
    
u = X/5
v = -Y/5
    
fig, ax = plt.subplots(figsize=(9,9))
    
ax.quiver(X,Y,u,v)
    
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
ax.set_aspect('equal')
    
plt.show()