segunda-feira, 20 de julho de 2020

Selecionando uma coluna com Pandas

Como já vimos nos posts anteriores selecionar uma coluna de um dataframe com Pandas retorna uma série. Uma série é um conjunto de dados organizados numa dimensão, diferente do dataframe que tem duas dimensões. Uma série é composta pelo índice e pelos dados. Com a biblioteca Pandas é possível criar uma série sem precisar de um dataframe, mais isso não é muito comum. O que geralmente acontece é criar uma série a partir de um dataframe.
Nesse tutorial vamos aprender a criar uma série de duas formas diferentes. No primeiro exemplo vamos usar o índice para criar uma série e no outro vamos criar uma série a partir de um atributo.

Como fazer isso...

Para criar uma série a partir de um dataframe, usando índices, o que precisamos fazer é passar o nome da coluna como se fosse um índice. Veja o exemplo abaixo:

>>> import pandas as pd
>>> filmes = pd.read_csv('filmes.csv', encoding='latin-1')
>>> filmes['Titulo no Brasil']
0                    PREÇO DA PAZ O
1                       CARTOMANTEA
2              BLACK & WHITE VOL. 9
3                    O GURU DO SEXO
4             O AMERICANO TRANQUILO
                       ...
9257          ROCK OF AGES: O FILME
9258                         MATRIX
9259                 MILITARY WIVES
9260    ROBERTO CARLOS EM JERUSALÉM
9261                         O POÇO
Name: Titulo no Brasil, Length: 9262, dtype: object

Criar uma série a partir de um atributo é ainda mais simples do que com índice. Veja um exemplo:

>>> import pandas as pd
>>> filmes = pd.read_csv('filmes.csv', encoding='latin-1')
>>> filmes.Diretor
0       PAULO DE TARSO DE CARVALHO MORELLI
1           WAGNER DE ASSIS E PABLO URANGA
2                LENILDO MAURICIO DA SILVA
3                              DAISY MAYER
4                            PHILLIP NOYCE
                           ...
9257                         ADAM SHANKMAN
9258         LANA WACHOWSKI ANDY WACHOWSKI
9259                        PETER CATTANEO
9260      MARIO HUMBERTO MEIRELLES MOREIRA
9261                 ANDRE BORELLI MARTINS
Name: Diretor, Length: 9262, dtype: object

Outra alternativa é utilizar os atributos loc e iloc. Com o atributo loc é possível conseguir uma série de uma coluna ou linha passando o seu nome. O atributo iloc funciona de modo semelhante ao atributo loc, a diferença é que o atributo iloc recebe o índice da coluna.
Veja um exemplo de como utilizar o atributo loc:

>>> import pandas as pd
>>> filmes = pd.read_csv('filmes.csv', encoding='latin-1')
>>> filmes.loc[:, 'Titulo Original']
0                    PREÇO DA PAZ O
1                       CARTOMANTEA
2              BLACK & WHITE VOL. 9
3                          THE GURU
4                    QUIET AMERICAN
                   ...             
9257                   ROCK OF AGES
9258                     THE MATRIX
9259                 MILITARY WIVES
9260    ROBERTO CARLOS EM JERUSALÉM
9261                         O POÇO
Name: Titulo Original, Length: 9262, dtype: object

Veja um exemplo de como utilizar o atributo iloc:

>>> import pandas as pd
>>> filmes = pd.read_csv('filmes.csv', encoding='latin-1')
>>> filmes.iloc[:, 1]
0                    PREÇO DA PAZ O
1                       CARTOMANTEA
2              BLACK & WHITE VOL. 9
3                          THE GURU
4                    QUIET AMERICAN
                   ...             
9257                   ROCK OF AGES
9258                     THE MATRIX
9259                 MILITARY WIVES
9260    ROBERTO CARLOS EM JERUSALÉM
9261                         O POÇO
Name: Titulo Original, Length: 9262, dtype: object

Nos dois exemplos acima acessamos todos os valores da coluna ‘Título Original’. No primeiro exemplo usamos o atributo loc. Com esse atributos passamos o nome da coluna que queremos acessar, no caso ‘Título Original’. Já com o atributo iloc passamos o número do índice da coluna que queremos acessar. No exemplo, logo acima, passamos o valor um assim temos acesso aos dados da segunda coluna (Lembrando que o índice começa em zero). Os dois pontos usados nos exemplos sinaliza que queremos os dados de todas as alinhas. Para selecionar um intervalo: filmes.iloc[42:100, 1].
Na saído dos dois atributos é possível notar o índice das linhas (rows), três pontos entre as linhas e, no final, o nome da coluna, o tamanho (Length) e o tipo de dados (dtype). O índice serve para saber qual é a posição do valor. Os três pontos sinalizam que alguns elementos não foram mostrados.
Um dataframe do Pandas, geralmente, tem duas ou mais colunas e cada uma dessas colunas pode ser tratada como uma série.
Nos exemplos acima ficou claro que podemos obter uma série de um dataframe de diferentes maneiras. O jeito mais fácil é como atributo. Uma coisa para prestar atenção na hora de nomear: os valores aceitos são os alfanuméricos sem espaços e sem caracteres especiais.

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()