NumPy, Introdução


Numpy

NumPy é uma biblioteca do python especializada em computação científica e análise de dados. Ela é usada em diversos tipos de operações que envolvem operações matriciais. Além disso suas matrizes formam a base para outros pacotes, como o pandas e outras voltadas para o cálculo matemático e científico. Numpy foi primeiro lançado por Travis Oliphant em 2006 e tem sido mantido por um grande número de colabores desde então, sob licença BSD.

NumPy, com suas matrizes, apresenta algumas vantagens sobre o cálculo usual com objetos do python, como listas e tuplas. Essas operações são mais rápidas e flexíveis e foram construídas de forma a evitar a necessidade da realização de laços (loops ). Ela contém métodos voltados para operações da álgebra linear, geração de números aleatórios e transformadas de Fourier, além da interface voltada para a conexão com as linguagens C, C++ e FORTRAN.

Um exemplo rápido pode mostrar como as rotinas do numpy são mais eficientes que as de objetos list do python.

» # comparação de velocidades
» import numpy as np
» # um array do numpy
» array = np.arange(1_000_000)
» # uma lista usual do python
» lista = list(range(1_000_000))

» %time for _ in range(100): arr2 = array * 2
↳ CPU times: user 145 ms, sys: 7.93 ms, total: 152 ms
  Wall time: 151 ms

» %time for _ in range(100): lista2 = [x * 2 for x in lista]
↳ CPU times: user 5.35 s, sys: 778 ms, total: 6.13 s
  Wall time: 6.12 s

» # 5.35/.145 ≈ 37 × mais rápido

Wall time é o tempo total gasto pelo código para ser executado. CPU time é uma medida do tempo gasto pelo processador apenas quando esteve operando sobre a tarefa específica. Dependendo do cálculo feito o numpy pode ser mais de 100 vezes mais rápido que uma operação similar em puro python.

Instalação


Em geral o módulo está presente como pacote na maioria das distribuições de Python. Se necessária a sua instalação em separado pode ser feita. Numpy e pandas são instalados juntos com o Anaconda.

# No Linux (Ubuntu and Debian):
» sudo apt-get install python-numpy
# No Linux (Fedora)
» sudo yum install numpy scipy
# No Windows com Anaconda:
» conda install numpy
# após a instalação o módulo deve ser importado:
» import numpy as np
# O aliás np é opcional e de escolha do programador.

Ndarray

O objeto básico da biblioteca Numpy é o ndarray (N-dimensional array). Ndarrays são matrizes multidimensionais com número determinado de dimensões e elementos. Seus elementos pertencem todos a um único tipo, chamado de dtype (data-type ou tipo de dado). Cada dimensão é denominada por axis (eixo) e o número de eixos é o rank do objeto. Diferente das listas do python, ndarrays têm dimensões fixas, definidas em sua construção.

Um ndarray possui os seguintes atributos referentes ao seu tipo de dado, tamanho e ordem:

Atributo descrição
array.dtype tipo de dado armazenado. (Veja lista abaixo),
array.ndim número de dimensões (que são eixos ou axis ); o mesmo que rank
array.size número de elementos em cada eixo,
array.shape (ou forma), tupla de N inteiros positivos com o comprimento de cada eixo.

Um ndarray de 1 dimensão é um objeto similar a um vetor (rank = 1): arr1D = ([a0,a1,...,aN-1,]), arr1D.size = N, arr1D.ndim = 1, arr1D.shape = (N,).

Um ndarray de 2 dimensões é um objeto similar a uma matriz (rank = 2): se ela possui M linhas, cada uma com N elementos então arr2D.size = M × N , arr2D.ndim = 2, arr2D.shape = (M,N).

Um ndarray de 3 dimensões é uma coleção de matrizes (rank = 3): se ela possui K matrizes de M linhas, cada uma com N elementos então arr3D.size = K × M × N , arr3D.ndim = 3, arr3D.shape = (K, M, N).

Ndarrays de ordem superior são generalizações desse processo, acrescentados novos eixos.

Os eixos são numerados para diversas operações. Em 2 dimensões axis=0 são as linhas, axis=1 as colunas, e assim consecutivamente para ordens superiores.

Tipos, dtypes

Além dos tipos usuais do python, a importação de Numpy disponibiliza um conjunto extendido de tipos ou dtypes.

dtype descrição
bool booleano (true ou false) armazenado como um byte
intX inteiro com sinal, X-bit (X=8,16,32, 64)
uintX inteiro sem sinal, X-bit (X=8,16,32, 64)
intc idêntical ao int C (em geral int32 ou int64)
intp inteiro usado para indexação (como C size_t; em geral int32 ou int64)
float_ o mesmo que float64
float16 meia precisão float: sign bit, 5-bit exponente, 10-bit mantissa
float32 simple precisão float: sign bit, 8-bit exponente, 23-bit mantissa
float64 dupla precisão float: sign bit, 11-bit exponente, 52-bit mantissa
complex_ o mesmo que complex128
complex64 complexo, representado por dois 32-bit floats (parte real e imaginária)
complex128 complexo, representado por dois 64-bit floats (parte real e imaginária)

Construção de um array

Um array do NumPy pode ser criado passando-se uma lista para construtor np.array(lista).

» import numpy as np

» lista = [123,234,345]
» arr = np.array(lista)
» arr
↳ array([123, 234, 345])

# o objeto criado é um ndarray do numpy
» type(arr)
↳ numpy.ndarray

» arr.dtype
↳ dtype('int64')

» arr.ndim
↳ 1
» arr.shape
↳ (3,)
» arr.size
↳ 3

» # uma lista de listas
» lista2 = [[123,234,345],
            [456,567,678],
            [789,890,901]]
» arr2 = np.array(lista2)
» arr2
↳ array([[123, 234, 345],
         [456, 567, 678],
         [789, 890, 901]])

» arr2.ndim
↳ 2
» arr2.size
↳ 9
» arr2.shape
↳ (3, 3)

» # lista de listas de listas
» lista3 =[ [ [1,2],[2,3] ], [ [3,4],[4,5] ],  [ [1,2],[2,3] ], [ [3,4],[4,5] ] ]
» arr3 = np.array(lista3)
» # o resultado é: 4 matrizes de 2 x 2  elementos
» arr3
↳ array([[[1, 2],
         [2, 3]],

        [[3, 4],
         [4, 5]],

        [[1, 2],
         [2, 3]],

        [[3, 4],
         [4, 5]]])

» arr3.ndim
↳ 3
» arr3.shape
↳ (4, 2, 2)
» arr3.size
↳ 16

» # a 1ª matriz
» arr3[0]

↳  array([[1, 2],
         [2, 3]])

» # a 2ª linha da 1ª matriz
» arr3[0,1]
↳ array([2, 3])

» # o 1º elemento da 2ª linha da 1ª matriz
» arr3[0,1,0]
↳ 2

# todos os arrays criados tem o mesmo dtype
» arr3.dtype
↳ dtype('int64')

Arrays podem ser de outros tipos, como um array de strings. No entanto devem ser homogêneos (todos os elementos do mesmo tipo). Uma tentativa de criar um array como em stArr2 causa uma tentativa de ajuste (cast), transformando os inteiros em string. O método array(listas, dtype) aceita o parâmetro dtype onde se pode informar o tipo de elemento que se pretende armazenar.

» stArr = np.array([['a', 'b'],['c', 'd']])
» stArr
↳ array([['a', 'b'],
       ['c', 'd']], dtype='<U1')

» stArr[0,1]
↳ 'b'
» stArr.dtype
↳ dtype('<U1')
» stArr.dtype.name
↳ 'str32'

» stArr2 = np.array([[1.01, 2.02],['h', 'i']])
» stArr2
↳ array([['1.01', '2.02'],
        ['h', 'i']], dtype='<U21')

» stArr3 = np.array([[True, False],['h', 'i']])
» stArr3
↳ array([['True', 'False'],
        ['h', 'i']], dtype='<U5')

» # parâmetro dtype
» cplx = np.array([[1, 2, 3],[4, 5, 6]], dtype=complex)
» cplx
↳ array([[1.+0.j, 2.+0.j, 3.+0.j],
        [4.+0.j, 5.+0.j, 6.+0.j]])               

Arrays podem ser transformados de um tipo para outro (quando possível). Para isso usamos array.astype(). Na transformação de floats para inteiros a parte decimal será truncada. Arrays de strings, desde que devidamente formatados, podem ser convertidos em numéricos.

» # criando um array de integers
» arr = np.array([1, 2, 3, 4, 5])
» arr.dtype
↳ dtype('int64')

» # cast para array de ponto flutuante
» floatArr = arr.astype(np.float64)
» floatArr.dtype
↳ dtype('float64')

» # floats para integers
» # criando um array de floats
» arr = np.array([1.9, -8.2, -9.6, 0.9, 2.3, 10.7])
» arr
↳ array([ 1.9, -8.2, -9.6, 0.9, 2.3, 10.7])

» # converte para inteiros (trunca parte inteira)
» arr.astype(np.int32)
↳ array([ 1, -8, -9, 0, 2, 10], dtype=int32)

» # um array de strings
» arrNumStrings = np.array(['0.0', '7.75', '-6.6', '100'], dtype=np.string_)
» arrNumStrings.astype(float)
↳ array([ 0., 7.75, -6.6 , 100.]

Métodos predefinidos de construção

O método np.arange(m,n,[p]) retorna um array de inteiros no intervalo (m, n], i.e., começando em m e terminando em n-1. Se o primeiro argumento for omitido m=0. Um terceiro argumento informa o p=passo, intervalo entre os valores da sequência. Em np.arange(m,n,p) m, n devem ser inteiros mas p pode ser um float.

O método np.linspace(m,n,p) retorna um array no intervalo (m, n), ambos os extremos incluídos, com p números igualmente espaçados.

np.random.random(n) retorna um array com n elementos aleatórios e np.random(m, n) retorna um array com shape = (m,n) e elementos aleatórios.

» # np.range(n)
» np.arange(10)
↳ array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

» # np.range(m,n)
» np.arange(5, 15)
↳ array([ 5,  6,  7,  8,  9, 10, 11, 12, 13, 14])

» # np.range(m,n,p)
» np.arange(10, 100, 10)
↳ array([10, 20, 30, 40, 50, 60, 70, 80, 90])

» np.arange(10, 20, .5)
↳ array([10. , 10.5, 11. , 11.5, 12. , 12.5, 13. , 13.5, 14. , 14.5, 15. ,
         15.5, 16. , 16.5, 17. , 17.5, 18. , 18.5, 19. , 19.5])

» # np.linspace(m,n,p)
» np.linspace(10, 20, 5)
↳ array([10. , 12.5, 15. , 17.5, 20. ])

» # a sequência pode ser decrescente
» np.linspace(20, 10, 5)
↳ array([20. , 17.5, 15. , 12.5, 10. ])

» # np.random.random(n)
» np.random.random(10)
↳ array([0.35433322, 0.54555179, 0.48783323, 0.5785414 , 0.76837232,
         0.69888297, 0.62492788, 0.33289321, 0.75068313, 0.95667854])

» # np.random.random(m,n)
» np.random.random((2,3))
↳ array([[0.11351481, 0.76831577, 0.27597676],
         [0.73130126, 0.7225559 , 0.54040225]])

Os métodos np.zeros((m,n)) e np.ones((m,n)) criam, respectivamente, ndarrays de zeros e uns com as dimensões dadas pela tupla (ou lista) no argumento. np.eye(m,n) gera um ndarray m × n com elementos 1 na diagonal, 0 fora dela. np.eye(n,n) é o mesmo que np.eye(n) ou np.identity(n), que são a matriz identidade de n dimensões.

» np.zeros((2,4))
↳ array([[0., 0., 0., 0.],
         [0., 0., 0., 0.]])

» np.ones((2,3))
↳ array([[1., 1., 1.],
         [1., 1., 1.]])

» np.eye(2,4)
↳ array([[1., 0., 0., 0.],
         [0., 1., 0., 0.]])

» np.identity(3)
↳ array([[1., 0., 0.],
         [0., 1., 0.],
         [0., 0., 1.]])

» np.eye(2,6)
↳ array([[1., 0., 0., 0., 0., 0.],
         [0., 1., 0., 0., 0., 0.]])

» np.eye(4,4)
↳ array([[1., 0., 0., 0.],
         [0., 1., 0., 0.],
         [0., 0., 1., 0.],
         [0., 0., 0., 1.]])

O método np.empty(m,n) permite a criação de arrays vazios, em geral destinados a serem preenchidos depois de sua criação por meio de algum cálculo ou leitura de dados. Nos exemplos criamos um array numérico vazio, arrN. Observe que não há garantia de que as entradas serão nulas. Em seguida criamos um array vazio de strings, com espaço para 3 caracteres (dtype='<U3′), e o preenchemos em um loop.

» # array numérico "vazio"
» arrN = np.empty((1,3))
» arrN
↳ array([[4.66896202e-310, 0.00000000e+000, 1.58101007e-322]])    

» # array de strings
» arr = np.empty((3,3), dtype='<U3')
» arr
↳ array([['', '', ''],
         ['', '', ''],
         ['', '', '']], dtype='<U3')

» for linha in range(arr.shape[0]):
»     for coluna in range(arr.shape[1]):
»         arr[linha,coluna] = 'a' + str(linha) + str(coluna)
» arr
↳ array([['a00', 'a01', 'a02'],
         ['a10', 'a11', 'a12'],
         ['a20', 'a21', 'a22']], dtype='<U3')

Alterando dimensões

Dado um array de uma única linha com r elementos podemos tranformá-lo em um array com shape = (m,n), desde que as dimensões sejam compatíveis, i.e., r = m × n. De fato, qualquer array pode ser transformado em outro se eles possuem o mesmo número de elementos (mesmo size ).

» arr = np.random.random(12)
» arr
↳ array([0.04276829, 0.76468762, 0.24807651, 0.75531679, 0.60327475,
         0.81704922, 0.08233836, 0.64112484, 0.55276595, 0.30669723,
         0.43989324, 0.60031761])

» # reshape
» arr.reshape(3,4)
↳ array([[0.04276829, 0.76468762, 0.24807651, 0.75531679],
         [0.60327475, 0.81704922, 0.08233836, 0.64112484],
         [0.55276595, 0.30669723, 0.43989324, 0.60031761]])

» np.linspace(0,10, 6).reshape(2,3)
↳ array([[ 0.,  2.,  4.],
         [ 6.,  8., 10.]])    

Indexação e fatiamento


Quando um array é criado ele recebe automaticamente um conjunto de índices. Um elemento pode ser lido ou alterado por meio de seu índice. Índices negativos contam de trás para frente, sendo arr[-1] o último elemento do array. Para selecionar (ou editar) vários elementos passamos uma lista de índices.

» arr = np.linspace(1,12, 12)
» arr
↳ array([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12.])

» # o 5º elemento
» arr[4]
↳ 5.

» # alterando o 5º elemento
» arr[4] = 100
» arr
↳ array([ 1.,  2.,  3.,  4.,  100.,  6.,  7.,  8.,  9., 10., 11., 12.])

» arr[-8]
↳ 100

» # lendo vários elementos
» arr[[0,2,5]]
↳ array([1., 3., 6.])

» # alterando vários elementos
» arr[[0,2,5]] = [10, 20, 30]
» arr
↳ array([ 10.,   2.,  20.,   4., 100.,  30.,   7.,   8.,   9.,  10.,  11.,  12.])

No caso de arrays bidimensionais os elementos do array são acessados pelos índices de suas linhas e colunas, sendo que arr[l,c] = al,c é o elemento da linha l e coluna c. Em objetos de ranks superiores cada índice se refere a um dos eixos.

Uma fatia ou slice do array é um subconjunto de elementos que pode ter o mesmo shape ou não. Para um vetor, digamos arr1D = [a0, a1, ..., aM] a fatia arr1D[m,n] = [am, ..., an-1], onde m ≥ 0, n ≤ M. Vale lembrar que o comprimento da fatia de um array unidimensional é arr1D[m,n].size = n-m.

» # outro teste para slices
» arr = np.arange(10)    # cria o array array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

» arr[6:9]
↳ array([6, 7, 8])

» arr[:3]
↳ array([0, 1, 2])

» arr[7:]
↳ array([7, 8, 9])

» # uma seção pode ser alterada
» arr[3:6] = -5
» arr
↳ array([ 0,  1,  2, -5, -5, -5,  6,  7,  8,  9])

» arr[7:] = -arr[7:]
» arr
↳ array([ 0,  1,  2, -5, -5, -5,  6, -7, -8, -9])

(†) Uma operação entre arrays de dimensões diferentes, como ocorre em arr[0:4] = -1 é chamado de propagação ou broadcasting. Para interagir com a 1ª parte da expressão a 2ª é transformada: -1 → arr[-1,-1,-1,-1]. Voltaremos a esse tópico.

Observe que uma fatia de um array é uma referência àquela parte do array e qualquer alteração feita na fatia se refletirá no array original. A notação arr[i:j] significa, claro, elementos de i-ésimo até (j-1)-ésimo. arr[:] significa todos os elementos do array.

» # criando um array de teste
» arr = (np.random.random(10)*10).round(1)
» arr
↳ array([3. , 1.5, 5.3, 1.3, 8.8, 9.8, 4.7, 0.1, 0.1, 0.6])
» fatia = arr[1:5]
» fatia
↳ array([1.5, 5.3, 1.3, 8.8])

# vamos alterar trecho da fatia
» fatia[1:3] = 0
» fatia
↳ array([1.5, 0. , 0. , 8.8])

» # o array original foi alterado
» arr
↳ array([3. , 1.5, 0. , 0. , 8.8, 9.8, 4.7, 0.1, 0.1, 0.6])

» # vamos alterar a fatia inteira
» fatia[:] = -10
» arr
↳ array([  3. , -10. , -10. , -10. , -10. ,   9.8,   4.7,   0.1,   0.1,  0.6])

» # valores específicos podem ser fornecidos (sem broadcast)
» fatia[:] = [-1,-2,-3,-4]
» arr
↳ array([ 3. , -1. , -2. , -3. , -4. ,  9.8,  4.7,  0.1,  0.1,  0.6])

Esse comportamento é útil quando se trabalha com array de dados muito grande e se deseja alterar apenas parte dele, lembrando que a biblioteca efetua suas operações mantendo os dados envolvidos na memória.

Em um array arr de 2 dimensões arr[m] é a m-ésima linha e arr[m,n] se refere ao elemento am,n, da m-ésima linha, n-ésima coluna. arr[m][n] é o mesmo am,n.

» # slices para dimensões mais altas
» arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
» arr
↳ array([[1, 2, 3],
         [4, 5, 6],
         [7, 8, 9]])

» # 2ª linha
» arr[1]
↳ array([4, 5, 6])

» arr[:1]
↳ array([[1, 2, 3]])

» # 1ª linha, 3º elemento
» arr[0,2]
↳ 3
» arr[0][2]
↳ 3

Uma cópia de um setor é uma referência para aquele setor, chamda de view ou visualização do segmento. Alterações feitas à view se refletam no array original, a menos que o método arr.copy() seja usado. Um array copiado dessa forma perde a referência com o array original e pode ser modificado independentemente.

» # slices em 3D:
» # criamos um array com shape (2,3,2)
» arr3D = np.arange(12).reshape(2,3,2)
» arr3D
↳ array([[[ 0,  1],
         [ 2,  3],
         [ 4,  5]],

        [[ 6,  7],
         [ 8,  9],
         [10, 11]]])

» # a 1ª matriz é
» arr3D[0]
↳ array([[0, 1],
        [2, 3],
        [4, 5]])
» # a 2ª linha da 1ª matriz é
» arr3D[0,1]
↳ array([2, 3])

» # seu 2º elemento
» arr3D[0,1,1] # o mesmo que arr3D[0,1][1]
↳ 1

» # copiamos um slice de 2 formas
» guardar = arr3D[0].copy()
» slice = arr3D[0]
» # ambos com os valores da  1ª matriz

» # alteramos toda a 1ª matriz
» arr3D[0] = 12
» # o array original foi alterado
» arr3D
↳ array([[[12, 12],
         [12, 12],
         [12, 12]],

        [[ 6,  7],
         [ 8,  9],
         [10, 11]]])
» # a cópia por referência foi alterada
» slice
↳ array([[12, 12],
         [12, 12],
         [12, 12]])

» # mas a cópia por valor não foi alterada
» guardar
↳ array([[0, 1],
         [2, 3],
         [4, 5]])

» # podemos restaurar o array aos seus valores originais
» arr3D[0] = guardar
» arr3D
↳ array([[[ 0,  1],
          [ 2,  3],
          [ 4,  5]],

         [[ 6,  7],
          [ 8,  9],
          [10, 11]]])


A notação de slice, idêntica à usada em listas do python, funciona em arrays. Para um array unidimensional arr1d[i:f] retorna do i-ésimo elemento até j-ésimo elemento (exclusive). Para um array de 2 dimensões arr2d[i:f] retorna i-ésima linha até j-ésima linha (exclusive). Se i é omitido o início é usado, se j é omitido o final é usado. Portanto arr2d[:2] significa as duas primeiras linhas do array (linha 0 e linha 1).

Slices ou segmentos múltiplos podem ser usados. Por exemplo, arr2d[m:n, r:s] são as linhas de m até n-1, colunas de r até s-1.

» # array 1d  (um vetor)  
» arr = np.array([1.2, 2.3, 3.4, 4.5, 5.6])
» arr[2:4]
↳ array([3.4, 4.5])

» # array 2d  (uma matriz)  
» arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
» arr2d
↳ array([[1, 2, 3],
         [4, 5, 6],
         [7, 8, 9]])

» arr2d[0:2]
↳ array([[1, 2, 3],
         [4, 5, 6]])

» arr2d[:2]
↳ array([[1, 2, 3],
         [4, 5, 6]])
       
» # slices múltiplos
» arr2d[:2, 1:]
↳ array([[2, 3],
         [5, 6]])

arr2d[:2, 1:] são as linhas 0 e 1, colunas de 1 em diante.

O indexador pode ser um array booleano, e os valores serão filtrados apenas se o índice for True. Essa operação pode ser muito útil para a implementação de filtragens de tipos diversos.

» # o indexador pode ser booleano
» ar1 = np.array([-1,0, 3, 7, -2, 10])
» ar2 = np.array([-2,10, 2, 9, -1, 8])

» # extrair apenas 0º e 2º elemento
» ar1[[True, False, True, False, False, False]]
↳ array([-1,  3])

» # elementos de ar1 maiores que 2
» ar1[ar1>2]
↳ array([ 3,  7, 10])

» # elementos de ar1 maiores que os de ar2
» ar1[ar1>ar2]
↳ array([-1,  3, 10])

Suponha que temos dados sobre alguns países, armazenados em forma tabular. O primeiro array contém os nomes dos países, como se fosse um cabeçalho da tabela seguinte. O segundo array contém dados numéricos de qualquer natureza, com 5 linhas e 4 colunas, cada coluna contendo dados relativos ao país no cabeçalho. Para esse exemplo geramos esses dados aleatoriamente, apenas para exibir a operação.

» arrPais = np.array(['Brasil', 'Chile', 'Brasil', 'Peru'])
» arrDados = np.random.randn(5,4).round(2)  # 5 linhas e 4 colunas

» print(arrPais, '\n', arrDados)
↳ ['Brasil' 'Chile' 'Brasil' 'Peru'] 
   [[-0.71  0.42 -0.52  0.63]
   [-1.12 -0.29  0.03  1.43]
   [ 0.99  0.45  1.08  0.53]
   [-0.78  0.18 -0.07  0.28]
   [-2.03  0.44  0.07  1.28]]

» # podemos exibir todas as linhas das colunas 0 e 2
» arrDados[:,[0,2]]
↳ array([[-0.71, -0.52],
         [-1.12,  0.03],
         [ 0.99,  1.08],
         [-0.78, -0.07],
         [-2.03,  0.07]])

» # alternativamente, as colunas que correspondem ao Brasil
» arrBrasil = arrDados[:, arrPais=='Brasil']
» arrBrasil
↳ array([[-0.71, -0.52],
         [-1.12,  0.03],
         [ 0.99,  1.08],
         [-0.78, -0.07],
         [-2.03,  0.07]])

» # na tabela do Brasil, suponha que valores negativos sejam insignificantes
» # podemos eliminá-los com uma filtragem
» arrBrasil[arrBrasil < 0] = 0
» arrBrasil
↳ array([[0.  , 0.  ],
         [0.  , 0.03],
         [0.99, 1.08],
         [0.  , 0.  ],
         [0.  , 0.07]])

» # a soma desses dados é
» arrBrasil.sum()
↳ 2.17

» # para exibir os demais dados (não do Brasil)
» arrDados[:, arrPais!='Brasil'] # ou arrDados[:, ~(arrPais=='Brasil')]
↳ array([[ 0.42,  0.63],
         [-0.29,  1.43],
         [ 0.45,  0.53],
         [ 0.18,  0.28],
         [ 0.44,  1.28]])

Observe que arrPais!='Brasil' é a negação de arrPais=='Brasil'. O parênteses em ~(arrPais=='Brasil') é necessário pois a negação ~ tem precedência sobre o teste de igualdade. Sem o parênteses ~arrPais seria avaliado primeiro, o que resultaria em erro pois o array não é booleano.

As ferramentas do pandas facilitam operações como essas.

Operações matemáticas

Operações são realizadas elemento a elemento (que abreviaremos para “e/e” nesse texto). Operações usuais de um array por um escalar são são propagadas entre o escalar e cada elemento do array. Operações entre arrays são feitas e/e, ou seja, realizadas entre elementos na mesma posição. Os arrays devem ter as mesmas dimensões.

Operações de incremento, tais como array += 1 (que significa array = array + 1) ou array *= 2 são realizados inplace (alteram o próprio array). Diversas outras operações do NumPy (e do pandas) são realizadas inplace enquanto em várias delas existe o parâmetro inplace = True/False que permite a decisão de qual caso se deseja naquele momento.

Operações podem ser feitas entre arrays retornados por funções (que retornam arrays).

» # operações com escalares    
» ar1 =  np.linspace(10, 20, 5)
» ar1
↳ [10.  12.5 15.  17.5 20. ]

» ar1 + 10
↳ [20.  22.5 25.  27.5 30. ]

» ar1 * 10
↳ [100. 125. 150. 175. 200.]

» ar1 / 10
↳ [1.   1.25 1.5  1.75 2.  ]

» ar1**2
↳ [100.   156.25 225.   306.25 400.  ]

» 1/arr1
↳ array([0.1       , 0.08      , 0.06666667, 0.05714286, 0.05      ])

» # operações entre arrays
» ar2 =  np.linspace(10, 50, 5)
» print(ar1)
» print(ar2)
↳ [10.  12.5 15.  17.5 20. ]
↳ [10. 20. 30. 40. 50.]

» ar1 + ar2
↳ array([20. , 32.5, 45. , 57.5, 70. ])

» ar1 * ar2
↳ array([ 100.,  250.,  450.,  700., 1000.])

» ar2 / ar1
↳ array([1.        , 1.6       , 2.        , 2.28571429, 2.5       ])

» # operações com arrays (2 × 3)
» ar3 = np.linspace(0,5, 6).reshape(2,3)
» ar4 = np.linspace(0,10, 6).reshape(2,3)

» print(ar3)
↳ [[0. 1. 2.]
   [3. 4. 5.]]

» print(ar4)
↳ [[ 0.  2.  4.]
   [ 6.  8. 10.]]

» ar3 + ar4
↳ array([[ 0.,  3.,  6.],
         [ 9., 12., 15.]])

» ar3 - ar4
↳ array([[ 0., -1., -2.],
         [-3., -4., -5.]])

» ar3 * ar4
↳ array([[ 0.,  2.,  8.],
         [18., 32., 50.]])
       
» # operações de incremento são realizados inplace
» ar3 +=1
» ar3
↳ array([[1., 2., 3.],
         [4., 5., 6.]])

» # seno e cosseno em np retorna um array e/e
» ar1 * np.sin(ar2)
↳ array([ -5.44021111,  11.41181563, -14.82047436,  13.03948031,
          -5.24749707])

» ar3 * np.cos(ar4)
↳ array([[ 0.        , -0.41614684, -1.30728724],
         [ 2.88051086, -0.58200014, -4.19535765]])

Comparações entre arrays resultam em arrays booleanos.

» ar5 = np.linspace(0,5, 6).reshape(2,3)
» ar6 = np.random.random(6).reshape(2,3).round(2)*5

» ar5
↳ array([[0., 1., 2.],
         [3., 4., 5.]])
» ar6
↳ array([[3.  , 2.45, 1.2 ],
         [3.1 , 1.55, 1.75]])

» arrMaior= ar5 > ar6
» arrMaior
↳ array([[False, False,  True],
         [False,  True,  True]])

Broadcasting: A operações feitas acima, entre um array e um escalar, transformam o escalar em um array de dimensões apropriadas (de mesmo shape) antes de sua realização. Essa operação se chama broadcasting:. Por exemplo:

» ar1 = np.array([0,1,2,3])
» ar2 = np.array([4,4,4,4])

» # a soma com um escalar
» ar1 + 4
↳ array([4, 5, 6, 7])

» # é  mesmo que
» ar1 + ar2
↳ array([4, 5, 6, 7])

» # os elementos são iguais
» ar1 + ar2 == ar1 + 4
↳ array([ True,  True,  True,  True])

# o mesmo ocorre com comparações
» ar1 ≥ 2
↳ array([False, False,  True,  True])

Uma forma de seleção diferente consiste em passar listas de valores como índices. O array retornado depende de como essas listas são passadas. Essa é técnica é chamada de fancy indexing (indexação sofisticada). Em qualquer dos casos abaixo os índices podem aparecer em qualquer ordem.

O slice array[[i,j,...]] contém as linhas array[i], array[j], etc. (O mesmo que array[:,[i,j,...]]).
O slice array[:,[i,j,...]] contém as colunas array[:,i], array[:,j], etc.
O slice array[[i,j,...]:[r, s,...]] é uma linha contendo os elementos array[i,r], array[j,s], etc.

» # fancy indexing (passando arrays como indices)
» # vamos construir um array 6 × 5 e atribuir seus valores um a um
» arr = np.empty((6,5))

» for linha in range(6):
»     for coluna in range(5):
»         arr[linha,coluna] = linha * 10 + coluna
        
» # o array obtido é (uma forma de identificar facilmente de que elemento se trata)
» arr
↳ array([[ 0.,  1.,  2.,  3.,  4.],
         [10., 11., 12., 13., 14.],
         [20., 21., 22., 23., 24.],
         [30., 31., 32., 33., 34.],
         [40., 41., 42., 43., 44.],
         [50., 51., 52., 53., 54.]])

» # podemos selecionar linhas (em qualquer ordem)
» arr[[3,5,1]]       # o mesmo que arr[[3,5,1],:]
↳ array([[30., 31., 32., 33., 34.],
         [50., 51., 52., 53., 54.],
         [10., 11., 12., 13., 14.]]) 

» # ou colunas (em qualquer ordem)
» arr[:,[3,1]]
↳ array([[ 3.,  1.],
         [13., 11.],
         [23., 21.],
         [33., 31.],
         [43., 41.],
         [53., 51.]])

» # fornecer duas listas (que devem ter o mesmo tamanho) tem efeito diferente,
» # retornando array de i dimensão com os índices dados nas listas
» arr[[0,3,4,1],[0,3,4,1]]
↳ array([ 0., 33., 44., 11.])

» arr[[0,3,4,1],[1,0,4,2]]
↳ array([ 1., 30., 44., 12.])

Funções universais

Funções universais, ou ufunc, são funções que agem e/e, sobre todos os elementos de um array, retornando outra array de mesmas dimensões. Essas operações são também chamadas de operações vetorializadas. Embora envolvam laços (loops ) esses são realizados internamente e de forma eficiente, de modo a agilizar os processos.

Uma tabela das funções universais é encontrada abaixo.

Método retorna
abs, fabs valor absoluto inteiros, floats, ou complexos
sqrt raiz quadrada (equivale a arr**0.5)
square elementos elevados ao quadrado (equivale a arr**2)
exp exponencial de cada elemento (ex)
log, log10, logaritmos naturais (de base e e base 10var>
log2, log1p logaritmos de base 2 e log(1 + x)
sign sinal: (1, 0, -1) (positivo, zero, negativo)
ceil teto, menor inteiro maior ou igual
floor piso, maior inteiro menor ou igual
rint arredonda para o inteiro mais próximo, preservando dtype
modf partes inteiras e fracionárias do array, em e arrays
isnan array booleano, se o valor é NaN (Not a Number)
isfinite array booleano, se cada valor é finito (non-inf, non-NaN)
isinf array booleano, se cada valor é infinito
cos, sin, tan funções trigonométricas
cosh, sinh, tanh funções trigonométricas hiperbólicas
arccos, arcsin, arctan arcos de funções trigonométricas
arccosh, arcsinh, arctanh arcos de funções trigonométricas hiperbólicas
logical_not array booleano, negação do array (equivalent to ~arr).

Exemplos de uso:

» # Funções universais
» arrBool = np.array([True, False, False, True])
» arrBool
↳ array([ True, False, False,  True])
» np.logical_not(arrBool)
↳ array([False,  True,  True, False])

» arr = np.linspace(0, 10, 6)
» arr -=5
» arr
↳ array([-5., -3., -1.,  1.,  3.,  5.])

» np.abs(arr)
↳ array([5., 3., 1., 1., 3., 5.])

» # não altera arr
» np.sign(arr)
↳ array([-1., -1., -1.,  1.,  1.,  1.])

» arr = arr/10 +4
» arr = arr.reshape(2,3)
» arr
↳ array([[3.5, 3.7, 3.9],
         [4.1, 4.3, 4.5]])

» np.modf(arr)
↳ (array([[0.5, 0.7, 0.9],
          [0.1, 0.3, 0.5]]),
↳  array([[3., 3., 3.],
          [4., 4., 4.]]))

Funções de Agregação


Funções de agregação são funções que realizam operações em todos os elementos do array, retornando um escalar(um número). Em sua maioria elas retornam cálculos estatíscos sobre os dados.

» ag = np.array([3.3, 12.5, 11.2, 5.7, 0.3])
» ag
↳ array([ 3.3, 12.5, 11.2,  5.7,  0.3])

» # outputs nos comentários
» ag.sum()       # 33.0
» ag.min()       # 0.3
» ag.max()       # 12.5
» ag.mean()      # 6.6
» ag.std()       # 4.6337889464238655
» ag.var()       # 21.472
» ag.argmin()    # 4
» ag.argmax()    # 1

» # o mesmo vale para arrays com outros shapes
» ag23 =  np.random.random(6).reshape(2,3) -.5
» ag23
↳ array([[ 0.10425075, -0.29335437, -0.36814244],
         [ 0.32986805,  0.17289794, -0.4568041 ]])

» ag23.mean()
↳ -0.08521402850598175

Diversas das operações podem ser feitas sobre o array inteiro ou sobre linhas ou colunas. Para isso podemos especificar axis = 0 para operações sobre elementos das colunas, axis = 1 para operações sobre elementos das linhas.

» arr = np.random.randn(4,5).round(2)
» arr
↳ array([[ 0.45, -0.11, -0.54, -0.97, -0.23],
         [ 1.65,  0.76, -0.39, -1.83,  0.02],
         [ 0.45, -1.22,  1.93,  1.92, -0.43],
         [-0.39,  2.01,  0.04,  0.67, -1.1 ]])

» # a soma de todos os elementos
» arr.sum()
↳ 2.689999999999999

» # soma sobre elementos de cada coluna
» arr.sum(axis=0)       # ou arr.sum(0)
↳ array([ 2.17,  0.37,  0.17, -2.49, -3.13])

» # soma sobre elementos de cada linha
» arr.sum(axis=1)       # ou arr.sum(1)
↳ array([-0.42, -1.67,  0.6 , -1.42])

» # produtos dos elementos das linhas
» arr.prod(axis=1).round(2)
↳ array([0.01, 0.02, 0.87, 0.02])

» # produtos dos elementos das colunas
» arr.prod(axis=0).round(2)
↳ array([-0.13,  0.21,  0.02,  2.28, -0.  ])


Funções básicas de agregação:

Função retorna
np.all booleano, True se todos os elementos no array são não nulos
np.any booleano, True se algum dos elementos no array é não nulo
np.sum soma dos elementos do array ou sobre eixo especificado †.
np.mean Média aritmética; arrays de comprimento nulo têm média = NaN
np.std, np.var variância e desvio padrão
np.max, np.min valor máximo e mínimo no array
np.argmax, np.argmin índices do valor máximo e mínimo no array
np.cumsum soma cumulativa dos elementos, começando em 0
np.cumprod produto cumulativo dos elementos, começando em 1

(†) Para arrays de comprimento nulo tem soma np.sum = NaN.

🔺Início do artigo

Bibliografia

  • Blair, Steve: Python Data Science, edição do autor, 2019.
  • Harrison, Matt: Learning Pandas, Python Tools for Data Munging, Data Analysis, and Visualization,
    Treading on Python Series, Prentiss, 2016.
  • Johansson, Robert: Numerical Python, Scientific Computing and Data Science Applications with Numpy, SciPy and Matplotlib, 2nd., Chiba, Japan, 2019.
  • McKinney, Wes: Python for Data Analysis, O’Reilly Media, Sebastopol CA, 2018.
  • McKinney, Wes, Pandas Development Team: pandas: powerful Python data analysis toolkit Release 1.2.1,
  • Miller, Curtis: Hands-On Data Analysis with NumPy and pandas, Packt Publishing, Birmingham, 2018.
  • Nelli, Fabio: Python Data Analytics With Pandas, NumPy, and Matplotlib, 2nd., Springer, New York, 2018.
  • Site AI Ensina: Entendendo a biblioteca NumPy, acessado em julho de 2021.
  • Site GeeksforGeeks: Python NumPy, acessado em julho de 2021.
  • Site W3 Schools: NumPy Tutorial, acessado em julho de 2021.
  • NumPy, docs.
  • NumPy, Learn.

Nesse site:

Dataframes: Resumo


Atributos e métodos do pandas.dataFrame


Nas tabelas usamos df para referenciar um dataframe do pandas. Quando necessária a interação com um segundo dataframe ele é designado por dfOutro. A abreviação e/e significa “elemento a elemento”, utilizada quando a operação é aplicada entre todos os elementos de um e outro dataframe, usando elementos na posição (linha, coluna).

Atributos dos dataframes

Atributo descrição
df.at(m,n) valor correspondente à linha=m, coluna=n
df.axes lista representando os eixos do df
df.columns rótulos das colunas do df
df.dtypes dtypes no df
df.flags propriedades associadas ao df
df.iat valor para um par linha/coluna
df.iloc indexação baseada em localização puramente inteira para seleção por posição
df.loc grupo de linhas e colunas por rótulo(s) ou matriz booleana
df.ndim número (int) de eixos/dimensões da matriz
df.shape tupla com a dimensionalidade do df
df.size número (int) de elementos no objeto
df.style um objeto Styler
df.values representação Numpy do df

Métodos dos dataframes

Exibição e consulta ao dataframe

df.at(m,n) valor correspondente à linha=m, coluna=n
df.attrs dict de atributos globais do objeto (experimental)
df.head([n]) as primeiras n linhas
df.info([verbose, buf, max_cols, memory_usage,…]) resumo conciso do dataframe
df.tail([n]) últimas n linhas
df.items() iteração sobre (nome da coluna, Series)
df.iteritems() iteração sobre (nome da coluna, Series)
df.iterrows() iteração sobre linhas de df como pares (index, Series)
df.itertuples([index, nome]) iteração sobre linhas do df como pares nomeados
df.memory_usage([index, deep]) uso de memória de cada coluna em bytes
df.nlargest(n, columns[, keep]) n primeiras linhas ordenadas por colunas em ordem decrescente
df.nsmallest(n, colunas[, manter]) n primeiras n linhas ordenadas por colunas em ordem crescente
df.nunique([axis, dropna]) número de elementos distintos no eixo especificado

Operações matemáticas

Método descrição
df.abs() valor absoluto de cada elemento
df.add(dfOutro[, axis, level, fill_value]) adição de df e dfOutro
df.apply(func[, axis, raw, result_type, args]) aplica função ao longo de um eixo do df
df.applymap(func[, na_action]) aplica uma função a um df e/e
df.div(dfOutro[, axis, level, fill_value]) divisão flutuante de df por dfOutro
df.divide(dfOutro[, axis, level, fill_value]) divisão flutuante de df por dfOutro
df.dot(dfOutro) multiplicação de df por dfOutro
df.eval(expr[, inplace]) avalia a string ‘expr’ contendo operações sobre colunas do df
df.ewm([com, span, halflife, alpha,…]) função exponencial ponderada (EW)
df.floordiv(dfOutro[, axis, level, fill_value]) divisão inteira do df por dfOutro
df.mod(dfOutro[, axis, level, fill_value]) módulo de df por dfOutro e/e
df.mul(dfOutro[, axis, level, fill_value]) multiplicação de df por dfOutro, e/e
df.multiply(dfOutro[, axis, level, fill_value]) multiplicação de df por dfOutro, e/e
df.pow(dfOutro[, axis, level, fill_value]) exponencial do df por dfOutro e/e
df.prod([axis, skipna, level, numeric_only, ...]) produto dos valores sobre o eixo especificado
df.product([axis, skipna, level, numeric_only, ...]) produto dos valores sobre o eixo especificado
df.radd(dfOutro[, axis, level, fill_value]) adição de df e dfOutro e/e, com suporte a fill_na
df.rdiv(dfOutro[, axis, level, fill_value]) divisão float de df por dfOutro e/e
df.rfloordiv(dfOutro[, axis, level, fill_value]) divisão inteira do df e dfOutro e/e
df.rmod(dfOutro[, axis, level, fill_value]) módulo de df e dfOutro e/e
df.rmul(dfOutro[, axis, level, fill_value]) multiplicação de df por dfOutro e/e
df.round([decimals]) arredonda elementos de df para um número dado de casas decimais
df.rpow(dfOutro[, axis, level, fill_value]) exponencial do df por dfOutro e/e
df.rsub(dfOutro[, axis, level, fill_value]) subtração de df e dfOutro e/e
df.rtruediv(dfOutro[, axis, level, fill_value]) divisão float de df por dfOutro e/e
df.sub(dfOutro[, axis, level, fill_value]) subtração de df por dfOutro e/e
df.sum([axis, skipna, level, numeric_only, ...]) soma dos valores sobre o eixo especificado
df.transform(func[, axis]) executa func em elementos de df, sobre eixo especificado
df.truediv(dfOutro[, axis, level, fill_value]) divisão float de df por dfOutro e/e

Formatação, Transposição, Ordenação

Método descrição
Transposição Tij = Mji
df.T a transposta de df
df.transpose(* args[, copiar]) transpor índices e colunas (obter a transposta)
df.droplevel(nível[, axis]) remove colunas com index ou níveis solicitados
df.melt([id_vars, value_vars, var_name,…]) Unpivot dataFrame para um formato largo
df.pivot([index, columns, values]) refaz df organizado por valores de index/column fornecidos
df.pivot_table([values, index, columns,…]) cria tabela dinâmica no estilo planilha como um df
df.sort_index([axis, level, ascendente, ...]) classifica objeto por rótulos (ao longo de um eixo)
df.sort_values ​​(by[, axis, ascending, inplace, ...]) ordena por valores ao longo do eixo especificado
df.stack([level, dropna]) empilha os níveis prescritos das colunas para o índice
df.swapaxes(axis1, axis2[, copy]) inverte eixos com seus valores respectivos
df.swaplevel([i, j, axis]) inverte níveis i e j em umMultiIndex
df.unstack([level, fill_value]) pivot um level dos rótulos de índice (necessariamente hierárquicos)
df.explode(column[, ignore_index]) transforma cada elemento de uma lista em uma linha, mantendo os índices
df.squeeze([axis]) comprime valores de eixo unidimensional para escalares

Métodos estatísticos

Método descrição
df.corr([method, min_periods]) calcula correlação de pares de colunas, excluindo valores NA/nulos
df.corrwith(dfOutro[, axis, drop, method]) calcula correlação de pares
df.count([axis, level, numeric_only]) quantos valores não NA para cada coluna ou linha
df.cov([min_periods, ddof]) calcula covariância de pares de colunas, excluindo NA/nulos
df.cummax([axis, skipna]) máximo cumulativo em um df
df.cummin([axis, skipna]) mínimo cumulativo sobre um eixo do df
df.cumprod([axis, skipna]) produto cumulativo sobre um eixo do df
df.cumsum([axis, skipna]) soma cumulativa sobre eixo do df
df.describe([percentis, include, exclude, ...]) gera descrição estatística
df.diff([points, axis]) primeira diferença discreta do elemento
df.hist([column, by, grid, xlabelsize, xrot,…]) histograma das colunas do df
df.info([verbose, buf, max_cols, memory_usage,…]) resumo conciso do dataframe
df.kurt([axis, skipna, level, numeric_only]) curtose imparcial sobre o eixo especificado
df.kurtosis([axis, skipna, level, numeric_only]) curtose imparcial sobre o eixo especificado
df.mad([axis, skipna, level]) desvio absoluto médio sobre especificado
df.max([axis, skipna, level, numeric_only]) máximo dos valores sobre o eixo especificado
df.mean([axis, skipna, level, numeric_only]) média dos valores sobre o eixo especificado
df.median([axis, skipna, level, numeric_only]) mediana dos valores sobre o eixo especificado
df.min([axis, skipna, level, numeric_only]) mínimo dos valores sobre o eixo especificado
df.mode([axis, numeric_only, dropna]) moda(s) dos elemento ao longo do eixo selecionado
df.pct_change([periods, method, limit, frequency]) alteração percentual entre o elemento atual e o anterior
df.quantil([q, axis, numeric_only, interpolateion]) valores no quantil dado sobre o eixo especificado
df.sample([n, frac, replace, weight,…]) amostra aleatória de itens de um eixo
df.sem([axis, skipna, level, ddof, numeric_only]) erro padrão imparcial da média sobre o eixo especificado
df.std([axis, skipna, level, ddof, numeric_only]) desvio padrão da amostra sobre o eixo especificado
df.var([axis, skipna, level, ddof, numeric_only]) variação imparcial sobre o eixo especificado

Gerenciamento, filtragem e consulta ao dataframe

Método descrição
df.add_prefix(prefixo) acrecenta prefixo nos rótulos (labels)
df.add_suffix(sufixo) acrecenta sufixo nos rótulos (labels)
df.agg([function, axis]) agregar usando function no eixo especificado
df.aggregate([função, axis]) agregar usando function no eixo especificado
df.align(dfOutro[, junção, axis, nível, cópia, ...]) alinha dois objetos em seus eixos com o método especificado
df.append(dfOutro[, ignore_index,…]) anexa linhas de dfOutro ao final de df
df.asof(where[, subset]) última(s) linha(s) sem NaN antes de where
df.assign(** kwargs) atribui novas colunas a um df
df.clip([lower, upper, axis, inplace]) corta (trim) os valores no(s) limite(s) dados
df.combine(dfOutro, func[, fill_value, ...]) combina colunas com dfOutro
df.combine_first(dfOutro) atualiza elementos nulos com elementos de dfOutro, na mesma posição
df.compare(dfOutro[, align_axis, keep_shape,…]) compara com dfOutro> e exibe diferenças
df.copy([deep]) faz cópia do objeto com índices e dados
df.drop([labels, axis, index, columns, level, ...]) remove linhas ou colunas com os rótulos especificados
df.drop_duplicates([subset, keep, inplace, ...]) remove linhas duplicadas
df.expanding([min_periods, center, axis, method]) aplica transformações de expansão
df.filter([itens, like, regex, axis]) filtra linhas ou colunas do df de acordo com os índices especificados
df.first_valid_index() índice do primeiro valor não NA; None se nenhum for encontrado
df.get(key[, default]) item do dataframe correspondente à chave fornecida
df.groupby([by, axis, level, as_index, ...]) agrupa df usando um mapper ou Series de colunas
df.infer_objects() tentativa de inferir dtypes para colunas do objeto
df.insert(loc, column, value[, allow_duplicates]) insere coluna no df no local especificado
df.join(dfOutro[, on, how, lsuffix, rsuffix, sort]) junta colunas de df e dfOutro
df.lookup(row_labels, col_labels) (descontinuado) “indexação extravagante” baseada em rótulos para df
df.mask(cond[, dfOutro, local, axis, level, ...]) substitui valores onde a condição é True
df.merge(right[, how, on, left_on, …]) mesclar df usando database de estilo
df.pipe(func, * args, ** kwargs) aplicação de func(self, * args, ** kwargs)
df.pop(item) remove e retorna o item removido
df.query(expr[, inplace]) consulta colunas de df com uma expressão booleana
df.rank([axis, method, numeric_only, ...]) classificações de dados numéricos (1 a n) ao longo do eixo
df.replace([to_replace, value, inplace, limit,…]) substitui valores em to_replace com value
df.rolling(window[, min_periods, center,…]) cálculos de “rolling window”
df.select_dtypes([include, exclude]) subset de colunas do df com base nos dtypes da coluna
df.skew([axis, skipna, level, numeric_only]) inclinação imparcial sobre o eixo especificado
df.slice_shift([perids, axis]) (descontinuado) Equivalente a shift sem copiar dados
df.sparse alias de pandas.core.arrays.sparse.accessor.SparseFrameAccessor
df.take(indices[, axis, is_copy]) elementos nos índices posicionais fornecidos ao longo de um eixo
df.truncate([before, after, axis, copy]) truncar df antes e depois de valores de índices
df.update(dfOutro[, juntar, substituir,…]) modifique “no local” usando valores não NA de dfOutro
df.value_counts([subset, normalize, …]) retorna série contendo número de linhas exclusivas no df
df.where(cond[, dfOutro, inplace, axis, level, ...]) substitui valores em que a condição é falsa
df.xs(key[, axis, level, drop_level]) seção transversal de df

Testes e comparações

Método descrição
df.empty booleano: se o df está vazio
df.all([axis, bool_only, skipna, nível]) booleano: se todos os elementos são verdadeiros, sobre um eixo (se especificado)
df.any([axis, bool_only, skipna, nível]) booleano: se algum elemento é verdadeiro, sobre um eixo (se especificado)
df.bool() booleano, se um único elemento do df é True/False
df.duplicated([subset, keep]) série booleana marcando linhas duplicadas
df.eq(dfOutro[, axis, level]) boolena: se elementos são iguais a um escalar ou dfOutro, e/e
df.equal(dfOutro) booleano: se dois objetos contêm os mesmos elementos
df.ge(dfOutro[, axis, level]) dataframe booleano, maior ou igual entre df e dfOutro, e/e
df.gt(dfOutro[, axis, level]) dataframe booleano, maior que, entre df e dfOutro, e/e
df.isin(values) booleano: se cada elemento no df está contido em values
df.le(dfOutro[, axis, level]) booleano: menor ou igual entre elementos de df e dfOutro, e/e
df.lt(dfOutro[, axis, level]) booleano: menor dos elementos de df e dfOutro, e/e
df.ne(dfOutro[, axis, level]) booleano, diferente entre df e dfOutro, e/e

Operações com rótulos (labels) e índices

df.index índices do df
df.keys() retorna o index
df.last_valid_index() índice do último valor não NA; None se nenhum valor NA for encontrado
df.reindex([labels, index, columns, axis, ...]) substitue índices de df, com lógica de preenchimento opcional
df.reindex_like(dfOutro[, method, copy, limit, ...]) retorna objeto com índices correspondentes à dfOutro
df.rename([mapper, index, columns, axis, copy, ...]) renomear rótulos dos eixos
df.rename_axis([mapper, index, columns, axis, ...]) define o nome do eixo para o índices ou colunas
df.reorder_levels(order[, axis]) reorganiza níveis de índices usando a ordem em order
df.reset_index([level, drop, inplace, ...]) redefine um índice ou seu nível
df.set_axis(rótulos[, axis, local]) atribui o índice desejado a determinado eixo
df.set_flags(*[, copy, allow_duplicated_labels]) novo objeto com sinalizadores (flags) atualizados
df.set_index(keys[, drop, append, inplace, ...]) define índices de df usando colunas existentes
df.idxmax([axis, skipna]) índice da primeira ocorrência do máximo sobre o eixo especificado
df.idxmin([axis, skipna]) índice da primeira ocorrência do mínimo sobre o eixo especificado

Plots

Método descrição
df.boxplot([column, by, ax, font-size, rot, ...]) traça gráfico de caixa usando as colunas do df
df.plot o mesmo que pandas.plotting._core.PlotAccessor
df.hist([column, by, grid, xlabelsize, xrot,…]) histograma das colunas do df

Serialização e conversões

Método descrição
df.astype(dtype[, copy, errors]) transforma objeto para um dtype especificado
df.convert_dtypes([infer_objects,…]) converte colunas para os melhores dtypes usando dtypes com suporte parapd.NA
df.from_dict(data[, orient, dtype, columns]) constroi df a partir de dict ou similar
df.from_records(data[, index, exclusion,…]) converte ndarray ou dados estruturados em dataframe
df.to_clipboard([excel, sep]) copia objeto para a área de transferência do sistema
df.to_csv([path_or_buf, sep, na_rep,…]) grava objeto em um arquivo de valores separados por vírgula (csv)
df.to_dict([orient, into]) converte df em um dicionário
df.to_excel(excel_writer[, sheet_name, na_rep,…]) grava objeto em uma planilha Excel
df.to_feather(path, **kwargs) grava df no formato binário Feather
df.to_gbq(destination_table[, project_id,…]) grava df em uma tabela Google BigQuery
df.to_hdf(path_or_buf, key[, mode, complevel,…]) grava objeto em um arquivo HDF5 usando HDFStore
df.to_html([buf, columns, col_space, header,…]) renderiza df como uma tabela HTML
df.to_json([path_or_buf, orient, date_format,…]) converte objeto em uma string JSON
df.to_latex([buf, colunas, col_space, cabeçalho,…]) renderiza objeto em uma tabela LaTeX
df.to_markdown([buf, mode, index, ...]) imprima df em formato Markdown
df.to_numpy([dtype, copy, na_value]) converte df em uma matriz NumPy
df.to_parquet([path, engine, ...]) grave df em formato binário parquet
df.to_period([freq, axis, copy]) converte df de DatetimeIndex para PeriodIndex
df.to_pickle(path[, compression, protocol, ...]) serializa o objeto para o arquivo pickle
df.to_records([index, column_dtypes, index_dtypes]) converte df em uma matriz de registro NumPy
df.to_sql(name, con[, schema, if_exists,…]) grava registros em df em um banco de dados SQL
df.to_stata(path[, convert_dates, write_index,…]) exporta df para o formato Stata dta
df.to_string([buf, columns, col_space, header, ...]) renderiza df em uma saída tabular compatível com o console
df.to_timestamp([freq, how, axis, copy]) converte para DatetimeIndex de timestamps, no início do período
df.to_xarray() converte para objeto xarray
df.to_xml([path_or_buffer, index, root_name,…]) renderiza df em um documento XML

Gerenciamento de valores ausentes

Método descrição
df.backfill([axis, inplace, limit, reduction]) o mesmo que df.fillna() com method = 'bfill'
df.bfill([axis, inplace, limit, downcast]) o mesmo que df.fillna() com method = 'bfill'
df.dropna([axis, how, treshold, subset, inplece]) remove os valores ausentes
df.ffill([axis, inplace, limit, reduction]) o mesmo que df.fillna() com method = 'ffill'
df.fillna([value, method, axis, local, ...]) preenche campos com NA/NaN usando o método especificado
df.interpolate([method, axis, limit, inplace, ...]) substitui valores NaN usando método de interpolação
df.isna() detecta valores ausentes
df.isnull() detecta valores ausentes
df.notna() valores existentes (não ausentes)
df.notnull() valores existentes (não ausentes)
df.pad([axis, inplace, limit, downcast]) o mesmo que df.fillna() com method = 'ffill'

Séries temporais e dados com hora/data

Método descrição
df.asfreq(freq[, método, como, normalizar, ...]) converte série temporal para a frequência especificada
df.at_time(hour[, asof, axis]) seleciona valores em um determinado horário do dia
df.between_time(start_time, end_time[,…]) seleciona valores entre horários especificados
df.first(offset) seleciona períodos iniciais em série temporal usando deslocamento (offset)
df.last(offset) selecione períodos finais da série temporal com deslocamento (offset)
df.resample(rule[, axis, closed, label, ...]) reamostrar os dados de série temporal
df.shift([periods, freq, axis, fill_value]) desloca índices por número desejado de períodos com uma frequância opcional
df.tshift([períodos, freq, axis]) (descontinuado) altera índice de tempo, usando a frequência do índice, se dispolevel
df.tz_convert(tz[, axis, level, copy]) converte o eixo com reconhecimento de tz em fuso horário de destino
df.tz_localize(tz[, axis, level, copy, ...]) localiza índice tz-naive de df para o fuso horário de destino

Ordenamento com dataframes.sort_values

Para ordenar um dataframe podemos usar o método sort, com a seguinte sintaxe:

dataframe.sort_values(by=['campo'], axis=0, ascending=True, inplace=False)
onde
by pode ser uma string ou lista com o nome ou nomes dos campos, na prioridade de ordenamento,
axis{0 ou ‘index’, 1 ou ‘columns’} default 0, indica o eixo a ordenar,
ascending=True/False se ordenamento é crescente/decrescente.

dataframe.reindex

Alterações da ordem dos índices de um dataframe podem ser obtidos com:
dataframe.reindex(listaDeCampos)
Os seguintes argumentos são usados com reindex

Argumento descrição
index Index ou sequência a ser usada como index,
method forma de interpolação: ‘ffill’ preenche com valor posterior, ‘bfill’ com valor anterior,
fill_value valor a usar quando dados não existentes são introduzidos por reindexing (ao invés de NaN),
limit quando preenchendo com valor anterior ou posterior, intervalo máximo a preencher (em número de elementos),
tolerance quando preenchendo com valor anterior ou posterior, intervalo máximo a preencher para valores inexatos (em distância numérica),
level combina Index simples no caso de MultiIndex; caso contrário seleciona subset,
copy se True, copia dados mesmo que novo índice seja equivalente ao índice antigo; se False, não copia dados quando índices são equivalentes.

Métodos e propriedades de Index

Método descrição
append concatena outro objeto Index objects, gerando novo Index
difference calcula a diferença de conjunto como um Index
intersection calcula intersecção de conjunto
union calcula união de conjunto
isin retorna array booleano indicando se cada valor está na coleção passada
delete apaga índice, recalculando Index
drop apaga índices passados, recalculando Index
insert insere índice, recalculando Index
is_monotonic retorna True se indices crescem de modo monotônico
is_unique returns True se não existem valores duplicados no Index
unique retorna índices sem repetições

Bibliografia

  • Pandas Pydata.org pandas docs, acessado em julho de 2021.

Python: Expressões regulares


Expressões regulares, também chamadas de regex (de regular expression), são meios de descrever padrões que podem ser encontrados dentro de um texto. Os padrões podem ser simples como a busca de um ou dois dígitos especificados, ou padrões complexos que incluem a posição do padrão no texto, o número de repetições, etc.
piada regex
Regex são usados basicamente para a busca de um padrão, substituição de texto, validação de formatos e filtragem de informações. Praticamente todas as linguagens de programação possuem ferramentas de uso de regex, assim como grande parte dos editores de texto. As diferentes linguagens de programação e aplicativos que incorporam funcionalidade de regex possuem sintaxes ligeiramente diferentes, cada uma, mas há uma embasamento geral que serve a todas. Existem aplicativos de teste de regex em diversas plataformas e aplicativos online para o mesmo fim.

Uma descrição das expressões regulares e seu uso estão no artigo Expressões Regulares (regex) deste site.

Módulo re, expressões regulares

No Python o módulo re, parte da biblioteca padrão, carrega uma “mini-linguagem” com meios de especificar tais padrões e realizar essas buscas e sibstituições.

O módulo possui os métodos que podem ser usados para encontrar padrões, partir texto e compilar padrões:

Método retorna
re.search(padrao, texto) 1º texto casado e sua posição, em todas as linhas,
re.match(padrao, texto) 1º texto casado e sua posição, na 1ª linha,
re.findall(padrao, texto) retorna uma lista com todos os trechos encontrados,
re.split(padrao, texto) parte o texto na ocorrência do padrão e retorna as partes,
re.sub(padrao, sub, texto) substitue em texto o padrao por sub,
re.subn(padrao, texto) similar à sub mas retorna tupla com nova string e número de substituições
re.compile(padrao) compila e retorna um padrão regex pre-compilado

Método re.search

resultado = re.search(padrao, texto)
O método search procura por um padrão dentro de um texto alvo e retorna um objeto re.Match que contém a posição inicial e final do padrão encontrado. Se o padrão não for encontrado o método retorna None.

O objeto re.Match possui o método group() que retorna o trecho encontrado, sendo que apenas a primeira ocorrência é considerada. Os parâmetros são padrao, uma construção regex, e texto, o conjunto de caracteres onde se busca o padrão. Em um texto de muitas linhas search procura em todas as linhas até encontrar o padrão, diferente do método match que procura apenas na primeira linha.

Uma letra, dígito ou conjunto de caracteres é casado literalmente. Se não encontrado None é retornado.

» import re
» texto = 'Este é um texto de teste para testar o funcionamento das expressões regulares'
» # procuramos por 'exp' no texto
» padrao = 'exp'
» resultado = re.search(padrao, texto)

» # o padrão 'exp' é encontado na posição 57 até 60
» print(resultado)
↳ <re.Match object; span=(57, 60), match='exp'>

» print(resultado.group())
↳ exp

» # a busca retorna None se o padrão não é encontrado
» print(re.search('z', texto))
↳ None

» # apenas o primeira coincidência é casada
» print(re.search('ste', texto))
↳ <re.Match object; span=(1, 4), match='ste'>

Como search() retorna None se não houver um casamento, podemos usar o retorno do método como critério de sucesso da busca. O padrão A{3} significa 3 letras A maiúsculas consecutivas, o que não existe no texto.

» texto = 'American Automobile Association'
» busca = re.search('A{3}', texto)
» if busca:
»     print(busca.group())
» else:
»     print('não encontrado')
↳ não encontrado    

Além de caracteres simples e grupos de caracteres os metacaracteres permitem ampliar o poder de busca das regex. Os textos casados são representados como em texto. Na tabela abaixo x representa um padrão qualquer.

padrão significado exemplo: casa ou não com
a caracter comum a casa Afazer aaa
ab grupos de caracteres comuns ab absoluto abraço Abcd trabalho
. casa com qualquer caracteres único m.to mato, mito, m3to
x* 0, 1 ou várias ocorrências de x 13* 1, 13456, 133, 13333-0987
x? 0, 1 ocorrência de x 13? 1, 13456, 133, 13333-0987
x+ 1 ou mais ocorrências de x 13+ 1, 13456, 133, 13333-0987
» # . = qualquer caracter
» print(re.search('p.ata', 'pirata pata prata').group())
↳ prata

» # x* = 0, 1 ou várias repetições de x
» print(re.search('jo*e', 'jose joo joe').group())
↳ joe

» print(re.search('jo*e', 'jose joo jooooooe').group())
↳ jooooooe

» # x? = 0 ou 1 ocorrência de x
» print(re.search('jo?e', 'jose jooe je').group())
↳ je

» print(re.search('jo?e', 'jose jooe joe').group())
↳ joe

» # x+ = 1 ou mais ocorrências de x
» print(re.search('jo+e', 'jose jooe joe').group())
↳ jooe

As chaves são usadas para quantificar repetições de um padrão.

padrão significado exemplo: casa ou não com
{n} significa exatamente n repetições do padrão 9{3} 999, 1999-45, 9-999, 999-00, 9, 99
{n,} mínimo de n repetições do padrão 9{2,} 99, 1999-45, 9-9999, 99999-00, 9, 9-9
{n,m} mínimo de n, máximo de m repetições do padrão 9{2,4} 99, 1999-45, 9-9999, 99999-00, 9, 9-9

O objeto re.Match possui diversos métodos:

Método retorna
match.group() a parte do texto casada com o padrão,
match.start() índice do início da parte do texto casada com o padrão,
match.end() índice do fim da parte do texto casada com o padrão,
match.span() os índices do início e do fim da parte do texto casada com o padrão,
match.re() a expressão regular casada (o padrão),
match.string() o texto passado como parâmetro.
» texto = 'Telefone: 05 (61) 3940-35356 (casa da Dinda), CEP: 123456789'

» # 4 digitos, hifen, 5 dígitos
» padrao = '\d{4}-\d{5}'

» # a variável resultado contém um objeto Match
» resultado = re.search(pattern, texto) 

» if resultado:
»     print(resultado.group())
»     print(resultado.start())
»     print(resultado.end())
»     print(resultado.span())    
» else:
»     print('Padrão não encontrado!')

↳ 3940-35356
↳ 18
↳ 28
↳ (18, 28)

» texto = 'CEP do cliente: 72715-620, DF'
» busca = re.search('\d+', texto)

» print(busca.start(), busca.end())
↳ 16 21

» # o primeiro trecho casado é retornado
» print(texto[busca.start(): busca.end()], '=', busca.group())
↳ 72715 = 72715

» busca = re.search('-\d+', texto)
» print(busca.group())
↳ -620

match.group(), que é o mesmo que match.group(0), se refere a todos os grupos encontrados. Se o padrão contém apenas um grupo só uma combinação é encontrada. Podemos construir padrões com mais de um grupos usando os marcadores de grupos, os parênteses ().

» texto = 'Telefone: 05 (61) 3940-35356 (casa da Dinda), CEP: 123456789'

» # 4 digitos (1º grupo), hifen, 5 dígitos (2º grupo)
» padrao = '(\d{4})-(\d{5})'
» resultado = re.search(padrao, texto) 

» # o 1º grupo combina com
» print(resultado.group(1))
↳ 3940

» # o 2º grupo combina com
» print(resultado.group(2))
↳ 35356

» # ambos os grupos
» print(resultado.group())
↳ 3940-35356

Parênteses () indicam um grupo, a ser procurado como um bloco. O sinal |indica uma alternativa onde um ou outro grupo é procurado.

» # procurando por mato ou mito
» print(re.search('m(a|i)to', 'moto mato mito').group())
↳ mato

» # só a primeira ocorrência é retornada
» print(re.search('m(a|i)to', 'moto muto mito').group())
↳ mito

» print(re.search('q{3}', 'q qq qqq').group())
↳ qqq
» print(re.search('q{,2}', 'qqqqq qq qqq').group())
↳ qq
print(re.search('q{2,}', 'qqqqqqqq qq qqq').group())
↳ qqqqqqqq

Um colchete [] delimita um conjunto alternativo de caracteres. O sinal |indica uma alternativa, um ou outro grupo é casado.

» print(re.search('pr[ae]to', 'prato, preto').group())
↳ prato

» print(re.search('pr[ae]to', 'proto, preto').group())
↳ preto

» # [0-9] representa qualquer dígito. '[0-9]{3,}' é grupo com mais de 3 dígitos:
» print(re.search('[0-9]{3,}', '6-45-4567-345345').group())
↳ 4567

» # grupo com até 2 dígitos
» print(re.search('[0-9]{,2}', '45-4567-345345').group())
↳ 45
» # \d é o mesmo que [0-9]
» print(re.search('\d{,2}', '45-4567-345345').group())
↳ 45

No Python uma “raw string” é uma sequência de caracteres que ignoram especiais do texto demarcado com \. '\ttexto' é “texto” após um espaçamento de tabulação mas r'\ttexto' é uma string simples. Na montagem de padrões é comum se usar “raw strings”.

» texto = '(casa): 72715-620, (escritório): 74854-890'

» busca = re.search('\(casa\)', texto)
» busca.group()
↳ (casa)

» # \t é tab
» print('\ttexto')
↳       texto
» # raw strings ignoram o metacaracter
» print(r'\ttexto')
↳ \ttexto

O padrão usado abaixo, padrao = ‘\+\d{2}\(\d{2}\)\d{5}-\\d{4}’ significa um número escrito como um telefone no formato + cód país (cod área) 5 dígitos – 4 dígitos.

» texto = '''Suponha que temos um texto com um número de telefone 
»            Telefone do cliente: +55(21)92374-4782
»            mais texto irrelevante'''

» padrao = '\+\d{2}\(\d{2}\)\d{5}-\\d{4}'
» fone = re.search(padrao, texto).group()

» print(fone)
↳ +55(21)92374-4782

Método re.findall

re.findall(padrao, texto)
O método findall encontra todas as ocorrências de padrao em texto e retorna uma lista com os trechos encontrados.

» import re    
» texto = 'Hoje 1 estamos 23 procurando 456 por 7890 números'
» padrao = '\d'
» resultado = re.findall(padrao, texto) 

» # \d = qualquer um dígito
» print(resultado)
↳ ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']

» # \d+ = qualquer um ou mais dígitos
» print(re.findall('\d+', texto))
↳ ['1', '23', '456', '7890']

» # \d{2} = grupos de 2 dígitos
» print(re.findall('\d{2}', texto))
↳ ['23', '45', '78', '90']

» # \d{3} = grupos de 3 dígitos
» print(re.findall('\d{3}', texto))
↳ ['456', '789']

» # \d{3,} = grupos de 3 ou mais dígitos
» print(re.findall('\d{3,}', texto))
↳ ['456', '7890']

» # \D+ = grupos de 1 ou mais não-dígitos
» print(re.findall('\D+', texto))
↳ ['Hoje ', ' estamos ', ' procurando ', ' por ', ' números']

» # caracteres na faixa de a até d (a, b, c, d)
» print(re.findall('[a-d]', texto))
↳ ['a', 'c', 'a', 'd']

» # dígitos na faixa de 1 a 4 (1,2 ,3, 4)
» print(re.findall('[1-4]', texto))
↳ ['1', '2', '3', '4']

» # texto 'oje' ou 'ando'
» print(re.findall('oje|ando', texto))
↳ ['oje', 'ando']

» # texto 'oje' ou 'ando' seguindos de qualquer sequência de caracteres
» print(re.findall('oje.*|ando.*', texto))
↳ ['oje 1 estamos 23 procurando 456 por 7890 números']

» # Obs. em qualquer busca o trecho casado é excluído de buscas posteriores.
» # o padrão 'ando.*' é ignorando

» # para encontrar no texto um padrão que contém metacaracteres usamos "raw strings"
» texto = 'Podemos usar \n para quebra de linha e \t para tabulações.'

» print(re.findall(r'[\n\t]', texto))
↳ ['\n', '\t']

Método re.split

resultado = re.split(padrao, texto, [maxsplit])
O método de split parte o texto em todas as ocorrências de padrao: e retorna uma lista com os trechos encontrados. Se o padrão não for encontrado uma lista com o texto inteiro é retornada. O parâmetro maxsplit (opcional) especifica o número máximo de cortes devem ser feitos no texto. O default é maxsplit = 0, signicando que todos os cortes possíveis serão feitos.

» import re
» texto = 'Hoje 1 estamos 23 procurando 456 por 7890 números'
» padrao = '\d+'
» resultado = re.split(padrao, texto) 

» # texto picado em toda ocorrência de 1 ou mais dígitos
» print(resultado)
↳ ['Hoje ', ' estamos ', ' procurando ', ' por ', ' números']

» # texto picado em toda ocorrência de espaços (\s)
» print(re.split('\s', texto))
↳ ['Hoje', '1', 'estamos', '23', 'procurando', '456', 'por', '7890', 'números']

» # padrão não encontrado
» print(re.split('w', texto))
↳ ['Hoje 1 estamos 23 procurando 456 por 7890 números']

» # especificando maxsplit = 2 (fazer apenas 2 cortes no texto)
» print(re.split('\d+', texto, 2))
↳ ['Hoje ', ' estamos ', ' procurando 456 por 7890 números']

Método re.sub

resultado = re.sub(padrao, subst, texto, [quantos])
O método re.sub procura um padrão e o substitui por um texto. A variável resultado é uma string com padrao substituído por subst. Se o padrão não é encontrado o texto original é retornado. O parâmetro opcional quantos indica quantas substituições devem ser feitas. O default é quantos = 0, o que significa que todas as ocorrências do padrão devem set substituídas.

» # remover todos os espaços em branco
» import re
» # texto com várias linhas e espaços em branco
» texto = 'Nome: Pedro \nSobrenome: Alvarez\nCabral'
» # padrão para casar com espaços (troca espaços por '')
» padrao = '\s'
» sub = ''
» resultado = re.sub(padrao, sub, texto) 

» print(resultado)
↳ Nome:PedroSobrenome:AlvarezCabral

» # padrão para substituir 1 ou mais espaços por espaço único
» texto = 'É comum  ter   textos  com dois ou mais espaços   inseridos onde  se deseja     apenas um!'

» print(texto)
↳ É comum  ter   textos  com dois ou mais espaços   inseridos onde  se deseja     apenas um!

» print(re.sub('\s+', ' ', texto) )
↳ É comum ter textos com dois ou mais espaços inseridos onde se deseja apenas um!

» # usando o parâmetro quantos
» texto = 'Esse texto possui 4 ocorrências de 3 dígitos repetidos: 012, 123, 234 e 345.'

» # Substituindo apenas as 2 primeiras ocorrências de 3 dígitos por ###
» print(re.sub('\d{3}', '###', texto, 2))
↳ Esse texto possui 4 ocorrências de 3 dígitos repetidos: ###, ###, 234 e 345.

Método re.subn

resultado = re.subn(padrao, subst, texto, [quantos])
O método re.subn é similar à re.sub mas retorna uma tupla de 2 itens, contendo a string modificada e o número de substituições feitas.

» texto = 'Temos as seguintes permutações de {a, b, c}: abc, acb, bac, bca, cab, cba.'
» resulta = re.subn('[abc]{3}', '|||', texto)
» print(resulta)
» print('Foram feitas {} substituições'.format(resulta[1]))

↳ ('Temos as seguintes permutações de {a, b, c}: |||, |||, |||, |||, |||, |||.', 6)
↳ Foram feitas 6 substituições

O método re.search recebe dois argumentos: um padrão e o texto a ser modificado. O método procura apenas pela primeira ocorrência do padrão. Se existe um casamento o método retorna um objeto match que contém a posição da coincidência (início e final) e a parte do texto que combina com o padrão. Se não houver nenhum casamento o método retorna None.

Método re.compile

padraoCompilado = re.compile(padrao, flags = 0)
O método re.compile() é especialmente útil quando o mesmo padrão será usado muitas vezes. Ele prepara um padrão através de uma pré-compilação e as armazena em cache que torna mais rápidas as buscas.

O método retorna um objeto re.Pattern que representa o padrao compilado sobre efeito dos parâmetros opcionais flags. Um exemplo é flag = re.I que determina que a busca será “insensível ao caso”. O objeto possui métodos que permitem as buscas pelo padrão dentro de um texto, tal como padrao.findall(texto), que retorna uma lista, ou padrao.finditer(texto) que retorna um iterável com os casamentos encontrados.

O padrão patt = ‘(xa|ma){2}’ significa um dos dois grupos, “xa” ou “ma”, repetidos 2 vezes.

» # 2 ocorrências de "xa" ou "ma"
» patt = '(xa|ma){2}'
» padrao = re.compile(patt)
» texto = 'xa, xaxado, ma, mamata, errata'
» busca = padrao.findall(texto)
» print(busca)
↳ ['xa', 'ma']

» # ocorrência de 5 dígitos juntos
» padrao = re.compile('\d{5}')
» texto = '12345 543213 858 9658 96521'
» busca = padrao.finditer(texto)
» for t in busca:
»     print(t.group())
↳ 12345
↳ 54321
↳ 96521

O objeto retornado, representado pela variável padraoCompilado acima, tem vários atributos, que podem ser vistos com a função dir(). Entre eles temos:

Flags ou sinalizadores

Os métodos do módulo re admitem um parâmetro extra chamado de flag (sinalizador ou marcador). Eles modificam o significado do padrão que se pretende buscar.

Os sinalizadores podem ser qualquer um dos seguintes:

Abreviado longo integrado (inline) significado
re.I re.IGNORECASE (?i) ignorar maiúsculas e minúsculas.
re.M re.MULTILINE (?n) força os localizadores ^ $ a considerarem uma linha inteira.
re.S re.DOTALL (?s) força . a casar com a newline, \n.
re.U re.UNICODE (?u) força \w, \W, \b, \B} a seguirem regras Unicode.
re.L re.LOCALE (?L) força \w, \W, \b, \B} a seguirem regras locais.
re.X re.VERBOSE (?x) permite comentários no regex.
» txt = 'estado, Estudo, estrume, ESTATUTO'
» r1 = re.findall('est[a-z]+', txt)
» r2 = re.findall('est[a-z]+', txt, flags=re.IGNORECASE)

» print(r1)
↳ ['estado', 'estrume']

» print(r2)
↳ ['estado', 'Estudo', 'estrume', 'ESTATUTO']

» # o mesmo resultado pode ser obtido com a notação inline
» re.findall('(?i)est[a-z]+', txt)
↳ ['estado', 'Estudo', 'estrume', 'ESTATUTO']

» re.findall('[a-z]+[dt]o', txt, flags=re.I)
↳ ['estado', 'Estudo', 'ESTATUTO']

Para usar mais de uma flag é possível separá-las com uma barra vertical (ou pipe). Por exemplo para uma busca multiline, insensível ao caso e com comentário:

re.findall(pattern, string, flags=re.I|re.M|re.X)

» texto = """
» Gato é um bicho engraçado.
» gato não é como cachoroo.
» Gato mia!
» """

» # a 1&orf; linha não começa com 'gato'
» re.findall("^gato", texto, flags=re.IGNORECASE)
↳ []

» # procurando em todoas as linhas
» re.findall("^gato", texto, flags=re.M)
↳ ['gato']

» # procurando em todoas as linhas, insensível ao caso
» re.findall("^gato", texto, flags=re.I | re.M)
↳ ['Gato', 'gato', 'Gato']

» # o mesmo resultado pode ser conseguido com flags inline
» re.findall("(?i)(?m)^gato", text)
↳ ['Gato', 'gato', 'Gato']

Exemplos

Um exemplo simples de remoção de tags aplicado a um texto HTML pode ser o seguinte: O padrão padrao = '<.*?>|[\n]' apenas casa com qualquer conteúdo dentro de <>, não guloso ou um sinal de quebra de linha, [\n]. Usando o método re.sub removemos todos os trechos que casam com esse padrão.

» html = '''
» <html>
» <body>
» <p>Parágrafo um.</p>
» <p>Parágrafo dois.</p>
» </body>
» </html>
» '''
» padrao = '<.*?>|[\n]'
» textoSemTags = re.sub(padrao, '', html)
» print(textoSemTags)    
↳ Parágrafo um.Parágrafo dois.

Existem bibliotecas sofisticadas para web scrapping, como Beautiful Soup que permite a busca, modificação e completa navegação de um documento extraído de uma página HTML. Buscas podem ser feitos por elementos de css, ids e classes e tags.

Padrões muito complexos são difíceis de serem lidos e alterados. Para quem programa em Python as buscas regex são geralmente ferramentas auxiliares que podem ser complementadas com manuseios do texto feitos em código.

Suponha que temos um texto no formato *.csv (valores separados por vírgulas) com 5 colunas. Na quarta coluna existe uma data com formato nem sempre consistente, como 26/06/2021 onde o ano pode ter apenas 2 dígitos e o separador pode ser um barra ou hífen. Queremos extrair o valor da quinta coluna quando o ano for posterior a 2015.

» csv = '''
» col1, col2, col3, data, valor
» a1  , a2   , a3  , 01/06/01, 1000
» b1  , b2   , b3  , 06/05/2016, 1000
» c1  , c2   , c3  , 4/3/17, 2000
» d1  , d2   , d3  , 14-12-2018, 600
» e1  , e2   , e3  , 19-09-19, 600
» '''

» for t in csv.split('\n'):
»     data  = t.split(',')
»     if len(data) != 5: continue
»     dt = data[3].strip()    
»     if not re.match('\d{1,2}[/|-]\d{1,2}[/|-]\d{2,4}', dt):  continue
»     ano = int(re.split('[/|-]',dt)[2])
»     ano = ano + 2000 if ano < 100 else ano
»     if ano > 2015:
»         print(ano, data[4])
↳ 2016  1000
↳ 2017  2000
↳ 2018  600
↳ 2019  600 

O texto é partido em linhas, cada linha em campos separados por vírgula. Como existem linhas vazias só são aproveitadas aquelas com 5 campos. Formatos de data não admissíveis são excluídos e uma correção para anos com apenas dois dígitos inserida.

Bibliografia

Python: Biblioteca Padrão


Vimos que um usuário pode gravar suas classes em módulos para depois importá-las para dentro de seu código. Nas instalações de Python um conjunto de módulos vem instalado por padrão, outros podem ser baixados e instalados pelo usuário.

A biblioteca padrão do Python é extensa e inclui recursos diversos voltados para muitas áreas de aplicação. Alguns desses módulos são escritos em C para garantir velocidade de execução, outros em Python. Uma lista completa desses módulos pode ser encontrada em Python Docs: Standard Library. Muitos outros módulos podem ser encontrados em Pypi: The Python Package Index. Esse últimos devem ser baixados e instalados antes de sua utilização.

Lista de alguns módulos da biblioteca padrão:

Módulo 📖 descrição (clique no ícone para ler a seção)
argparse processamento de argumentos em comando de linhas,
datetime 📖 classes para manipulação de datas e horas,
decimal tipo de dados decimais para aritmética de ponto flutuante,
doctest ferramenta para validar testes embutidos em docstrings de um módulo,
glob 📖 manipulação de listas de arquivos e pastas usando coringas,
io 📖 gerenciamento de IO (input/output),
math 📖 matemática de ponto flutuante,
os 📖 funções para a interação com o sistema operacional,
pprint habilidade pretty print para estruturas de dados,
random 📖 geração de números e escolhas aleatórios ,
re 📖 processamento de expressões regulares,
reprlib versão de repr() personalizada para exibições de contêineres grandes,
shutil 📖 interface com arquivos e coleções de arquivos,
statistics 📖 funções estatísticas (média, mediana, variância, etc.),
string 📖 métodos para formatação de strings,
textwrap formata parágrafos de texto para determinada largura de tela,
threading executa tarefas em segundo plano,
time 📖 acesso ao clock do computador,
timeit 📖 medidas de intervalo de tempo,
unittest conjunto de testes que podem ser mantidos em arquivo separado,
zlib, gzip, bz2, lzma, zipfile, tarfile suporte a formatos de compactação e arquivamento de dados.

Vamos exibir alguns dos métodos disponibilizados por esses módulos.

Módulo os

O módulo os foi tratado na seção Arquivos e pastas dessas notas.

Módulo io

Um buffer de dados (ou apenas buffer) é uma região de um armazenamento da memória física usado para armazenar dados temporariamente, antes de serem processados e gravados em memória permanente. Geralmente buffers são implementados pelo software na memória RAM e são normalmente usados ​​quando os dados são recebidos em taxa superior àquela em que podem ser processados. É o que ocorre em um spooler de impressora ou em streaming de vídeo online.

Os métodos read() e write(), que permitem a abertura de arquivos foram vistos em Arquivos e Pastas. No entanto os métodos em io são mais poderosos e flexíveis.

Arquivos binários e de texto

Arquivos podem ser abertos para escrita, para leitura e para acrescentar dados. O Python permite especificar dois tipos de arquivos: binário e texto. O modo binário é usado para lidar com todos os tipos de dados não textuais, como arquivos de imagem e arquivos executáveis. Qualquer arquivo é armazenado em forma binária. Mas, quando um arquivo de texto é aberto (e o comando de abertura informa que é um texto) esses bytes são decodificados para retornar objetos string. Já os binários retornam o conteúdo como sequências de bytes, sem decodificação.

Por exemplo, podemos abrir um arquivo binário e gravar nele a sequência algunsBytes = b'\xC3\xA9'. O prefixo b indica que essa é uma sequência binária.

# ilustrar gravação de arquivo binário e texto
» algunsBytes = b'\xC3\xA9'
  
» # abra um arquivo no modo 'wb' para escrever, no modo binário
» with open('./dados/arquivo.txt', 'wb') as arquivoBinario:
»     # escreve os bytes no arquivo
»     arquivoBinario.write(algunsBytes)
    
» # lendo esse arquivo no modo binário
» with open('./dados/arquivo.txt', 'rb') as f:
»     print(f.read())
↳ b'\xc3\xa9'

» # lendo esse arquivo no modo texto  (o código corresponde à letra "é")
» with open('./dados/arquivo.txt', 'r') as f:
»     print(f.read())
↳ é

Abrindo o arquivo no modo binário encontramos nossa sequência b’\xC3\xA9′. Quando verificamos esse arquivo no modo texto, usando o mode r ou abrindo em um editor de texto verificamos que está gravada a letra é (e com acento agudo). Fisicamente esse caracter fica gravado em disco (ou armazenado em RAM) como a sequência binária 11000011 10101001.

Outro exemplo curto consiste na leitura de um arquivo de imagem ./dados/Anaconda.png no modo binário, de leitura. O arquivo Anaconda.png deve estar na pasta indicada. Esses dados são depois escritos em ./dados/Novo.png, aberto no mode binário, para escrita. O resultado é a criação de Novo.png, uma imagem idêntica à original.

» with open('./dados/Anaconda.png', 'rb') as f:
»     with open('./dados/Novo.png', 'wb') as novo:
»         novo.write(f.read())

O módulo io é usado para gerenciar streams (fluxo de dados, I/O ou E/S em português). Esses fluxos podem ser, basicamente, de três tipos: dados de textos, binários ou RAW. Concretamente esses fluxos transferem objetos denominados arquivos.

Todos esses três tipos de objetos possuem atributos: podem ser só de leitura, gravação ou leitura e somente gravação. Eles podem ser acessados de modo de acesso aleatório (onde buscas são feitas em qualquer sentido, procurando por partes em qualquer local; ou de acesso apenas acesso sequencial, onde passos para trás não são permitidos, dependendo do tipo de dados lido.

As duas classes de uso mais comum são:

StringIO operações em fluxos de strings.
BytesIO operações em fluxos de bites,
BytesIO operações em fluxos de bites,

Encoding

Encoding ou codificação de texto é uma forma de representação de dados que contém letras, símbolos, números e marcações especiais (como quebra de linhas e tabulações). O forma de codificação mais comum para conversão de caracteres é a American Standard Code for Information Interchange (ASCII), desenvolvida no início da computação. Essa tabela contém apenas 128 posições, o que engloba apenas caracteres da língua inglesa. Para representar outros caracteres, com acentuação, de outras línguas e generalizar os símbolos se criou a codificação Unicode, que engloba todos os caracteres usados mundialmente. O UTF-8 (8-bit Unicode Transformation Format) é um tipo de codificação compacta para o código Unicode. Ele pode representar qualquer caractere universal padrão do Unicode, sendo também compatível com o ASCII. Por esta razão, está lentamente a ser adaptado como tipo de codificação padrão para e-mail, páginas web, e outros locais onde os caracteres são armazenados.

Exemplos de UNICODE e utf-8:

UNICODE caracter utf-8
U+00E0 à c3 a0
U+00E1 á c3 a1
U+00E2 â c3 a2
U+00E3 ã c3 a3
U+00E4 ä c3 a4

Quando se usa open() (e TextIOWrapper) para ler um arquivo é usada uma codificação especificada como sistema local. Sistemas Unix e seus derivados (como o Linux) usam por default a codificação UTF-8. No entanto existem sistemas (como o Windows) que não o fazem. Por isso é importante especificar explicitamente a codificação.

import io
» with io.open(filename,'r',encoding='utf8') as f:
»     text = f.read()

Para deixar o arquivo lido com a codificação local use encoding='locale'.

Classe io.StringIO()

O construtor de io.StringIO() permite a construção de um stream de dados tipo texto.

» import io
» arq = io.ioStringIO("Visitação aberta para o site\nPhylos.net")
» type(arq), type(arq.getvalue())
↳ (_ioStringIO, str)

» print(arq.getvalue()) # ou arq.read()
↳ Visitação aberta para o site
↳ Phylos.net

» # o objeto pode ser sobreescrito
» arq.write('Não deixem de visitar o')
↳ 23

» print(arq.getvalue())
↳ Não deixem de visitar o site
↳ Phylos.net

» arq.close()
» print(arq.getvalue()) 
↳ ValueError: I/O operation on closed file

StringIO constroi um objeto similar a um arquivo, residente na memória. Esse objeto pode ser usado como input ou output para funções que recebem arquivos padrões como argumentos. io.StringIO() inicializa um objeto vazio. Em qualquer dos casos o cursor sobre o arquivo está na posição 0. Os dados podem ser sobrepostos com io.StringIO('novo texto') que será inserido na posição do cursor (0, no caso). Após a inserção o cursor ficará na posição final do texto inserido, como mostrado no exemplo.

Depois do uso o objeto deve ser fechado com ioStr.close(). O objeto tipo arquivo é iterável como um arquivo usual. O fechamento após o uso garante a liberação de recursos usados. Nenhuma operação pode ser realizada sobre um arquivo fechado.

» f = io.StringIO()
» f = io.StringIO('linha 1\nlinha 2\nlinha 3')
» while True:
»     line = f.readline()
»     if (not line): break
»     else:print(line, end='')
» f.close() 

O método seek(n) permite apontar o cursor para o n-ésimo caracter. No código lemos apenas a primeira linha. Mesmo após o esgotamento das linhas seek(0) pode retornar o cursor para o início.

» with io.StringIO('linha 1\nlinha 2\nlinha 3') as f:
»     f.seek(3)
»     print(f.readline())
↳ ha 1
    
» with io.StringIO('linha 1\nlinha 2\nlinha 3') as f:
»     while True:
»         line = f.readline()
»         if (not line):
»             f.seek(0)
»             print(f.readline())  # só a 1ª linha é impressa
»             break    
↳ linha 1

O método arquivo.tell() informa o posição atual do cursor.

» with io.StringIO('Um cavalo! Um cavalo! Meu reino por um cavalo!') as f:
»     f.seek(22)
»     print('Da posição', f.tell(), 'em diante está o texto:', f.readline())
↳ Da posição 22 em diante está o texto: Meu reino por um cavalo!

O método file.getvalue() lê o arquivo inteiro independentemente da posição do cursor, enquanto file.read() lê à partir da posição do cursor até o final. O método file.truncate(n) corta o arquivo, mantendo apenas os caracteres de 0 até n, exclusive.

» file = io.StringIO('I\'ll be back!')
» print('1', file.getvalue())
» print('2', file.read())
» print('3', file.read())
» file.seek(0)
» print('4', file.read())

↳ 1 I'll be back!
↳ 2 I'll be back!
↳ 3 
↳ 4 I'll be back!

» file.truncate(7)
» print(file.getvalue())
↳ I'll be

Na linha 1 o arquivo foi lido mas o cursor continua no início. Em 2 o conteúdo é exibido e o arquivo é esgotado (cursor no final). Em 3 não há o que exibir. Em 4 o cursor é retornado ao início e o arquivo todo exibido.

O objeto StringIO tem os seguintes métodos e propriedade booleanas:

Métodos significado
StringIO.isatty() True se o arquivo é interativo,
StringIO.readable() True se o arquivo é está em modo leitura (readable),
StringIO.writable() True se o arquivo está no modo de escrita,
StringIO.seekable() True se o arquivo está no modo de acesso aleatório (random access),
Propriedade significado
StringIO.closed True se o arquivo está fechado.

A expressão arquivo na tabela significa um objeto tipo-arquivo, que não precisa estar gravado em disco. Um objeto que não está no modo de acesso aleatório não admite reposicionamento de cursor com o método seek(n).

» import io
» file = io.StringIO('Um texto qualquer de teste!')
» print("O arquivo é interativo?", file.isatty())
» print("O arquivo é de leitura?", file.readable())
» print("O arquivo está no modo de escrita?", file.writable())
» print("O arquivo admite buscas?", file.seekable())
» print("O arquivo está fechado?", file.closed)
» file.close()
» print("O arquivo foi fechado?", file.closed)
​
↳ O arquivo é interativo? False
↳ O arquivo é de leitura? True
↳ O arquivo está no modo de escrita? True
↳ O arquivo admite buscas? True
↳ O arquivo está fechado? False
↳ O arquivo foi fechado? True

Concluindo: io.StringIO fornece um meio conveniente de trabalhar com texto na memória usando métodos de arquivos como read(), write(). Com ele se pode construir strings longa com economia de desempenho em relação a outras técnicas de strings. Buffers de fluxo na memória também são úteis para testes uma vez que suas operações são mais ágeis que a gravação em um arquivo em disco.

Classe io.BytesIO

A classe io.BytesIO permite a inicialização de objetos de stream, puramente binários. Um arquivo aberto no modo binário não fará nenhuma conversão para caracteres de texto codificados, como o fazem os editores de texto, ou os arquivos abertos com io.StringIO. Eles são úteis para a manipulação de arquivos gerais, tais como imagens ou sons.

Um string prefixado com o caracter b é um string de bytes. O argumento de BytesIO deve ser sempre texto puro, em ASCII, ou um erro é lançado.

» import io
» arq = io.BytesIO(b'ç')
↳ SyntaxError: bytes can only contain ASCII literal characters.

» # usada com um argumento válido
» arq = io.BytesIO(b'Este texto, em bytes, sobre Python: \x00\x01')

» # o conteúdo é um string de bytes
» type(arq.getvalue())
↳ bytes

» # os caracteres \x00\x01 não são decodificados
» print(arq.getvalue())
↳ b'Este texto, em bytes, sobre Python: \x00\x01'

» # um arquivo fechado não pode ser manipulado
» arq.close()
» arq.getvalue()
↳ ValueError: I/O operation on closed file.

Um objeto BytesIO pode ser criado vazio e preenchido depois. BytesIO.write(b_str) insere b_str no final do objeto (faz um append). No exemplo abaixo dois strings comuns são transformados em bytes: '中文'.encode('utf-8') e 'criação'.encode('utf-8'), ambos envolvendo caracteres fora da tabela ASCII pura.

» arq = io.BytesIO()
» type(arq)
↳ io_BytesIO

» arq.write('中文'.encode('utf-8'))
» print(arq.getvalue())
↳ b'\xe4\xb8\xad\xe6\x96\x87'

» # encode() transforma em bytes os caracteres extendidos
» # no caso '中文' = 'chinês'
» type('中文'.encode('utf-8'))
↳ bytes

» # removendo o conteudo de arq e inserindo novo b_string
» arq = io.BytesIO()
» arq.write('criação'.encode('utf-8'))
» print(arq.getvalue())
↳ b'cria\xc3\xa7\xc3\xa3o'

» # write faz o append
» arq.write(b' <---')
» print(arq.getvalue())
↳ b'cria\xc3\xa7\xc3\xa3o <---'

Operações com io.BytesIO() são similares às de uma gravação de arquivo obtidas com file.open() e file.write(). Mas, embora criando um objeto com características de um arquivo, eles não precisam ser gravados em disco (ou outro meio de armazenamento) o que torna essas operações muito mais rápidas.

Uma comparação de velocidade entre gerar e manipular uma string com conteúdo binário e um objeto io.BytesIO() é feita abaixo.

» import io
» import time

» start = time.time()
» buffer = b''
» for i in range(0,100_000):
»     buffer += b'Oi Mundo!'
» print('1:', time.time() - start)

» start = time.time()
» buffer = io.BytesIO()
» for i in range(0,100_000):
»     buffer.write(b'Oi Mundo!')
» print('2:', time.time() - start)

» # o output será
↳ 1: 6.659011363983154
↳ 2: 0.012261390686035156

Se necessário podemos passar o conteúdo de um objeto io.BytesIO() para um arquivo ordinário e gravá-lo em disco.

    
» # criamos um buffer vazio e depois o preenchemos
» buffer = io.BytesIO()
» for i in range(0,100_000):
»     buffer.write(b'Oi Mundo!')
» # gravamos no disco esse buffer
» with open('./dados/Buffer.bin', 'wb') as arquivo:
»     arquivo.write(buffer.getbuffer())
» # um arquivo com 100000 linhas de 'Oi Mundo' é gravado no disco    

io.open()

Já vimos e usamos o método open() do módulo os. O módulo io também possui um método open().

» import io
» arquivo = io.open('./dados/Anaconda.png', 'rb', buffering = 0)
» print(arquivo.read())
» # o resultado está exibido abaixo (truncado)
↳ b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x1d\x00\x00\x01\x14\x08\x06\x00\x00\x00\x16e\xe2\xb3\x00\x00\x01\x84iCCPICC profile\x00\x00(\x91}\x91=H\xc3@\x1c\xc5_[E\xd1\xfa\x01f\x10q\xc8P\x9d,\x88\x8a8j\x15\x8aP!\xd4\n\xad:\x98\\\xfa\x05M\x1a\x92\x14\x17G\xc1\xb5\xe0\xe0\xc7b\xd5\xc1\xc5YW\x07WA\x10\xfc\x00qquRt\x91\x12\xff\x97\x14Z\xc4xp\xdc\x8fw\xf7\x1ew\xef\x80`\xad\xc44\xabm\x1c\xd0t\xdbL\xc6cb:\xb3*v\xbc\xa2\x1b\xbd\x10\xd0\x8f\x88\xcc,cN\x ...

O método io.open() é preferido para operações de E/S por ser interface de alto nível. Na verdade ele é um wrapperque se utiliza de os.open() mas retorna um objeto com mais propriedades e métodos.

RAW

O modo RAW (também chamado de IO sem buffer) é usada como um bloco de construção de baixo nível para fluxos binários e de texto. Esse modo raramente é útil manipular diretamente um fluxo do código do usuário. Se necessário um fluxo bruto pode ser criado abrindo-se um arquivo no modo binário e com o buffer desativado:

» f = open('myfile.jpg', 'rb', buffering=0)

Módulo shutil

O módulo shutil contém vários métodos que operam em arquivos e pastas, usados para copiar, apagar, entre outras funcionalidades. Vamos carregar os módulos os e shutil para experimentar alguns desses métodos. No Linux o sinal ‘./’ indica a pasta atual, ‘./Temp’, é uma subpasta de nome Temp.

Método significado
os.system() Executa um comando shell,
shutil.copy(origem, destino) copia arquivos,
shutil.copy2(origem, destino) copia arquivos com metadados,
shutil.move(origem, destino) move arquivos,
shutil.copyfile(origem, destino) copia arquivos,
shutil.copytree(pastaOrigem, pastaDestino) copia pasta e seu conteúdo
shutil.rmtree(pastaDestino) paga pastas recursivamente,
shutil.disk_usage(caminho) dados de uso do disco,
shutil.which(app) caminho de um aplicativo no path.

Exemplos de uso:
shutil.copy(origem, destino)
O método shutil.copy copia o arquivo origem para o destino, retornando uma exceção se a pasta de destino não existir.

shutil.copy2(origem, destino)
O método shutil.copy2 tem o mesmo efeito que o anterior, mas tenta copiar junto com o arquivo origem os seus metadados para o destino.

shutil.move(origem, destino)
O método shutil.move move o arquivo origem para o destino, retornando uma exceção se a pasta de destino não existir. O arquivo original deixa de existir.

» import os
» import shutil

» origem = './renomear.py'
» destino = './temp/renomear.py'

» # Vamos copiar o arquivo renomar.py para a paste temp de destino
» dest = shutil.copy(origem, destino)

» # um arquivo pode ser copiado em sua própria pasta
» shutil.copy('nomeAntigo.txt','nomeNovo.txt')

» # um arquivo pode ser renomeado
» shutil.move('nomeAntigo.txt','nomeNovo.txt')

» # o método retorna a pasta e arquivo de destino
» print('Pasta de destino:', dest)
↳ Pasta de destino: ./temp/renomear.py

» shutil.move('nomeAntigo.txt', , 'nomeNovo.txt')
↳ 'nomeNovo.txt'

» # considerando que nomeNovo.txt é o único arquivo na pasta
» os.listdir('./temp')
↳ ['nomeNovo.txt']

No Windows os caminhos devem ser escritos de forma compatível com o sistema.

» import shutil
» import os

» caminho = 'C:\\temp1\\temp2'
» print('A pasta atual contém os arquivos:') 
» print(os.listdir(caminho))
» # os arquivos são listados

» destino = 'C:\\temp1\\temp3'
» shutil.move('arquivo.txt',destino + '\\temp3\\arquivo.txt')

» print('A pasta de destino passa a ter os arquivos:') 
» print(os.listdir(destino))
» # uma lista é impressa, incluindo arquivo.txt

shutil.copyfile(origem, destino)
Um arquivo pode ser copiado com shutil.copyfile, origem é o caminho relativo ou absoluto da arquivo a ser copiado, destino o caminho do arquivo copiado.

shutil.copytree(pastaOrigem, pastaDestino)
O método shutil.copytree copia uma pasta inteira juntamente com todos os seus arquivos e outras pastas, de pastaOrigem para pastaDestino. A pasta de destino é criada na cópia ou uma exceção é lançada se a pasta ou os arquivos já existirem no destino.

» # copiando um arquivo da pasta ativa para Temp do usuário
» origem = './teste.txt'
» destino = '/home/usuario/Temp/testeNovo.txt'
» shutil.copytree(origem, destino)
	
» origem = '/home/usr/Projetos/'
» destino = '/home/usr/Projetos/temp2'

» shutil.copytree(origem, destino)
» print(os.listdir(destino))
» # uma lista dos arquivos no destino é exibida

shutil.rmtree(pastaDestino)
Para apagar pastas recursivamente podemos usar shutil.rmtree. Um erro será lançado de
a pasta pastaDestino não existir ou se não for uma pasta (se for um arquivo).

» print(os.listdir('./'))
↳ ['texto1.txt', 'alunos.csv', 'temp', 'temp2']

» # apagar pasta temp2 e todos os seus arquivos
» shutil.rmtree('./temp2')

» print(os.listdir('./'))
↳ ['texto1.txt', 'alunos.csv', 'temp']	

shutil.disk_usage(caminho)
shutil.which(app)
O método shutil.disk_usage informa dados de uso do disco sendo acessado e shutil.which(app) informa o caminho completo do aplicativo app que seria executado se estiver no PATH acessável no momento. O método retorna None se o app não existir ou não estiver no PATH.

» caminho = '/home/usuario/'
» estatistica = shutil.disk_usage(caminho)
» print(estatistica)
↳  usage(total=234684264448, used=172971024384, free=49720573952)

» comando = 'python'
» local = shutil.which(comando) 
» print(local)
↳ /home/guilherme/.anaconda3/bin/python

» comando = 'firefox'
» locate = shutil.which(comando) 
» print(local)
↳ /usr/bin/firefox

Biblioteca glob

O módulo glob fornece métodos para a busca de arquivos e pastas usando padrões de construção de nomes semelhantes aos usados pelo shell do Unix.

Método Descrição
glob.glob(padrao) retorna lista de arquivos que satisfazem o padrão em padrao,
glob.iglob(padrao) retorna um iterador contendo os nomes dos arquivos individuais,
glob.escape(padrao) Usado nos casos em que os nomes de arquivos usam caracteres especiais.

Usando o módulo glob é possível pesquisar por nomes de arquivo literais ou usar caracteres especiais, similares aos das expressões regulares (mas bem mais simples).

Sinal Descrição
* Asterisco: corresponde a zero ou mais caracteres,
? Interrogação: corresponde exatamente a um caractere,
[] Colchetes: intervalo de caracteres alfanuméricos,
[!] negativa: não contém os caracteres listados.

A sintaxe de glob é:

glob.glob(caminho, recursive = False)
glob.iglob(caminho, recursive = False)
onde caminho é o nome dos arquivo ou arquivos, usando os sinais acima e caminho relativo ou absoluto. Se recursive = True a busca é realizada recursivamente dentro das subpastas. O método iglob tem o mesmo efeito mas retorna um objeto iterável. Alguns exemplos são:

» import glob
» import os

» # retorna todos os arquivos na pasta /home/usuario/Temp/
» glob.glob('/home/usuario/Temp/*')
↳ ['/home/usuario/Temp/musica02.mp4',
↳  '/home/usuario/Temp/Musica01.mp3',
↳  '/home/usuario/Temp/teste.txt',
↳  '/home/usuario/Temp/Programa.py']

» # arquivos com nomes começados com letra minúscula 
» glob.glob('/home/usuario/Temp/[a-z]*')
↳ ['/home/usuario/Temp/musica02.mp4',
↳  '/home/usuario/Temp/teste.txt']

» # arquivos com nomes começados com letra maiúscula 
» glob.glob('/home/usuario/Temp/[A-H]*')
↳ ['/home/usuario/Temp/Programa.py',
↳  '/home/usuario/Temp/Musica01.mp3']

» # arquivos com extensão mp(0 até 9)
» glob.glob('/home/usuario/Temp/*.mp[0-9]')
↳ ['/home/usuario/Temp/Musica01.mp3',
↳   '/home/usuario/Temp/musica02.mp4']

» # podemos trocar a pasta ativa
» os.chdir('/home/usuario/Temp/')
» glob.glob('*')
» # retorna todos os arquivos na pasta

» # iglob retorna um iterável
» it = glob.iglob('*')
» for t in it:
»     print(t)
↳ /home/usuario/Temp/musica02.mp4
↳ /home/usuario/Temp/Musica01.mp3
↳ /home/usuario/Temp/teste.txt
↳ /home/usuario/Temp/Programa.py

» # usando a negativa
» # arquivos que não começam com m, M ou t.
» glob.glob('[!mMt]*')
↳ ['/home/usuario/Temp/Programa.py']

glob.escape(caminho, recursive = False)
O método escape permite o uso de caracateres especiais como _, #, $ no nome dos arquivos. O caracter especial é inserido por meio de glob.escape(char).

» caminho = '*' + glob.escape('#') + '*.jpeg'
» glob.glob(caminho)
↳ ['Foto#001.jpeg']

» caminho = 'A*' + glob.escape('_') + '*.gif'
» glob.glob(caminho)
↳ ['Abcd_001.gif']

Um exemplo de uso pode ser dado no código que procura e apaga arquivos temporários, com a extensão .tmp

» import glob
» import os

» # apague arquivos temporários, tipo *.tmp
» for t in (glob.glob('*.tmp')):
»     print('Apagando ', t)
»     os.remove(t)

Módulo math

O módulo math do Python contém diversas funções matemáticas, especialmente úteis em projetos científicos, de engenharia ou financeiros. Eles são um acréscimo aos operadores matemáticos básicos do módulo básico, como os operadores matemáticos integrados, como adição (+), subtração (-), divisão (/) e multiplicação (*).

As seguintes funções são encontradas no módulo:

Método descrição
Funções trigonométricas e geométricas
math.sin() seno, ângulo em radianos,
math.cos() cosseno,
math.tan() tangente,
math.asin() arco seno,
math.acos() arco cosseno,
math.atan() arco tangente em radianos,
math.atan2() arco tangente de y/x em radianos,
math.sinh() seno hiperbólico,
math.cosh() cosseno hiperbólico,
math.tanh() tangente hiperbólica,
math.asinh() inverso do seno hiperbólico,
math.acosh() inverso do cosseno hiperbólico,
math.atanh() inversa da tangente hiperbólica,
math.degrees() ângulo convertido de radianos para graus,
math.radians() ângulo convertido de grau para radianos,
math.dist() distância euclidiana entre 2 pontos p e q,
math.hypot() norma euclidiana, distância da origem,
Exponencial e logarítmica
math.pow() potência, xy,
math.erf() função de erro de um número,
math.erfc() a função de erro complementar de um número,
math.exp() exponencial de x, ex,
math.expm1() exponencial de x menos 1, ex-1,
math.frexp() mantissa e expoente de número especificado,
math.log() logaritmo natural, ln(x),
math.log10() logaritmo de base 10, log10(x),
math.log1p() logaritmo natural de 1 + x, , ln(1+x),
math.log2() logaritmo de base 2, log2(x),
math.ldexp() inverso de math.frexp() que é x×2i,
math.gamma() função gama,
math.lgamma() gama do log de x,
Arredondamentos e truncamentos
math.ceil() número arredondado para o maior inteiro mais próximo,
math.floor() número arredondado para o menor inteiro mais próximo,
math.copysign() float, valor do primeiro parâmetro com o sinal do segundo parâmetro,
math.trunc() número truncado, parte inteira,
math.fabs() valor absoluto,
Análise combinatória
math.comb() quantas formas de k itens de n sem repetição e ordem,
math.perm() permutações, maneiras de escolher k itens de n com ordem, sem repetição,
math.factorial() fatorial,
Funções sobre iteráveis
math.prod() produto dos elementos de um iterável,
math.fsum() soma dos elementos de um iterável,
Outras funções
math.fmod() resto da divisão x/y,
math.remainder() resto da divisão x/y,
math.gcd() mdc, maior divisor comum de inteiros,
math.lcm() mdc, mínimo múltiplo comum de inteiros,
math.sqrt() raiz quadrada,
math.isqrt() raiz quadrada arredondada para o menor inteiro mais próximo,
Testes
math.isclose() booleano, se dois valores estão próximos,
math.isfinite() booleano, se um número é finito,
math.isinf() booleano, se um número é infinito,
math.isnan() booleano, se um valor é NaN.

As seguintes constantes são carregadas:

Constante valor
math.e número de Euler (2.7182…),
math.inf infinito positivo de ponto flutuante,
math.nan NaN positivo de ponto flutuante,
math.pi π = 3.1415…,
math.tau τ = 2 π.

Alguns exemplos de operações:

» import math
» math.cos(0)        # 1.0
» math.sin(0)        # 0.0
» math.ceil(1.17)    # 2
» math.floor(1.75)   # 1
» math.fabs(-1.75)   # 1.75
» math.comb(7,3)     # 35,   comb(n,k) = n! / (k! * (n - k)!) se k <= n, 0 se  k > n
» math.copysign(3.7,-4.5) # -3.7
» math.fmod(125,3)   # 2.0
» math.fsum([1.2, 2.3, 3.4, 4.5]) # 11.4  (qualquer iterável no argumento)
» math.gcd(12, 8)    # 4 mdc (máximo divisor comum)
» math.pow(2,15)     # 32768.0
» math.sqrt(135)     # 11.61895003862225
» p, q = [1,2,3], [3,2,1]
» math.dist(p,q)     # 2.8284271247461903
» math.hypot(1,2,3)  # 3.741657386773941

Módulos cmath e Numpy

As funções do módulo math são todos definidas para parâmetros “reais” (números de ponto flutuante, pra ser mais preciso). Elas não podem receber números complexos como parâmetros. Para operações com complexos existe o módulo cmath que implementa muitas das mesmas funções. Mais informações sobre esse módulo em Python Docs.


Para operações matemáticas mais sofisticadas existem várias bibliotecas de uso específico. Uma das mais usadas é Numerical Python ou NumPy, usado principalmente na computação científica e no tratamento de dados. NumPy não faz parte da biblioteca padrão e deve ser instalado separadamente.

Além de diversas funções semelhantes às do módulo math, NumPy é capaz de realizar operações alto desempenho com matrizes n-dimensionais. Frequentemente NumPy é utilizado junto com o pandas (veja artigo) para a análise de dados, treinamento de máquina e inteligência artificial.

Módulos statistics e random

O módulo statistics contém diversos métodos para o cálculo de funções estatísticas sobre um conjunto de dados numéricos. A maioria dessas funções podem receber como parâmetros dados int, float, Decimal e Fraction.

Método descrição
Médias e medidas de localização central
mean() Média aritmética dos dados,
fmean() Média aritmética rápida, de ponto flutuante,
geometric_mean() Média geométrica dos dados,
harmonic_mean() Média harmônica dos dados,
median() Mediana,
median_low() Mediana baixa,
median_high() Mediana alta,
median_grouped() Mediana ou 50-ésimo percentil de dados agrupados,
mode() Moda (o valor mais comum) de dados discretos,
multimode() Lista de modas (valores mais comuns) de dados discretos,
quantiles() Divide dados em intervalos de igual probabilidade,
Medidas de espalhamento
stdev() Desvio padrão,
pstdev() Desvio padrão,
variance() Variância da amostra,
pvariance() Variância de toda a população.

Alguns exemplos simples de cálculos estatísticos:

» from statistics import *
» mean([1.2, 2.3, 3.4, 4.5, 5.6])
↳ 3.4

» mode([1.2,2.1,2.1,3.8,4.6])
↳ 2.1

» # mode pode ser usada com dados nominais
» mode(['carro', 'casa', 'casa', 'carro', 'avião', 'helicóptero', 'casa'])
↳ 'casa'

» # multimode retorna lista com valores mais frequentes, na ordem em que aparecem 
» multimode('aaaabbbccddddeeffffgg')
↳ ['a', 'd', 'f']

» multimode([1,1,1,2,3,3,3,4,5,5,6,6,6])
↳ [1, 3, 6]

» dados = [1.5, 2.5, 2.5, 2.75, 3.25, 4.75]
» pstdev(dados)
↳ 0.986893273527251

» # statistics.pvariance(data, mu=None): variação populacional de dados
» # se mu (ponto central da média) não for fornecido ele é calculado como a média
» # se for conhecido pode ser passado como parâmetro

» mu = mean(dados)
» pstdev(dados, mu)
↳ 0.986893273527251

» from fractions import Fraction as F
» dados = [F(1, 4), F(5, 4), F(1, 2)]
» pvariance(dados)
↳ Fraction(13, 72)

» pvariance([1/4, 5/4, 1/2])   # o mesmo resultado seria obtido com floats:  13/72 =
↳ 0.18055555555555555

» # desvio padrão
» dados = [1.25, 3.12, 2.15, 5.68, 7.23, 3.01]
» stdev(dados)
↳ 2.2622643523691037	

Muitos outras bibliotecas existem para cálculo estatístico no Python, como NumPy e SciPy, alémm de outros aplicativos como Minitab, SAS, Matlab e R (nesse site).

Módulo random

Números aleatórios ou randômicos gerados em computadores são números produzidos à partir de uma semente ( ou seed) por meio de algoritmos que visam produzir valores o mais espalhados e imprevisíveis quanto possível. Como os algoritmos são todos determinísticos a distribuição não será de fato aleatória, a menos que o computador possa estar conectado a um evento externo realmente randômico (como o decaimento de uma substância radioativa).

Para fins práticos, tais como uso em jogos, teste e geração de chaves criptográficas, a pseudo aleatoriedade pode ser suficiente.

O módulo random contém diversos métodos para geração desses números e de escolhas entre listas.

Método descrição
seed() inicilaliza o gerador de números aleatórios,
getstate() retorna o estado atual do gerador,
setstate() restaura o estado do gerador,
getrandbits() retorna um número representando bits aleatórios,
randrange() retorna um número aleatório dentro do intervalo,
randint() retorna um inteiro aleatório dentro do intervalo,
choice() retorna um elemento aleatório da sequência dada,
choices() retorna uma lista de elementos aleatórios da sequência dada,
shuffle() embaralha os elementos de lista, retorna None,
sample() retorna uma amostra da sequência,
random() retorna número de ponto flutuante entre 0 e 1,
uniform() retorna float no intervalo,
triangular() retorna float no intervalo, com possibilidade de determinar o ponto central.

Os métodos abaixo retornam um número de ponto flutuante (float) entre 0 e 1 baseado na distribuição mencionada:

Método distribuição
betavariate() Beta,
expovariate() Exponential,
gammavariate() Gamma,
gauss() Gaussiana,
lognormvariate() log-normal,
normalvariate() normal,
vonmisesvariate() de von Mises,
paretovariate() de Pareto,
weibullvariate() de Weibull.

O método random() retorna um número randômico entre 0 e 1 (no intervalo [0, 1)).

seed(semente) é usado para inicializar o gerador. A partir de uma determinada inicialização os números gerados sempre se repetem, o que é útil para testes, em execuções repetidas do código. Se nenhuma semente for fornecida a semente default é a hora atual do sistema.

» # número entre 0 (inclusive) e 1 (exclusive)
» random.random()
↳ 0.15084917392450192

» # a semente inicializa o gerador
» # o bloco seguinte sempre gera os mesmos números
» random.seed(10)
» for t in range(5):
»     print(random.random(), end=' ')
↳ 0.5714025946899135 0.4288890546751146 0.5780913011344704 0.20609823213950174 0.81332125135732     	

» # inteiros entre 1 e 10 (inclusive)
» for t in range(10):
»     print(int(random.random()*10+1), end=' ')
↳ 3 9 8 9 10 3 9 8 5 4

» # o mesmo se pode conseguir com randint
» for t in range(10):
»     print(random.randint(1,10), end=' ')
↳ 4 1 6 1 5 3 5 2 7 10 

O método choice(lista) retorna um ítem escolhido aleatoriamente entre membros de uma lista, tupla ou string. sample(lista, k) escolhe k elementos da lista.

» import random
» lista = [123, 234, 345, 456, 567, 678,789]
» print(random.choice(lista))
↳ 789

» lista = ['banana', 'laranja', 'abacaxi', 'uva', 'pera']
» print(random.choice(lista))
↳ abacaxi

» # escolhe um elemento de uma string
» texto = 'Phylos.net'
» print(random.choice(texto)	
↳ h

» # podemos simular o experimento de 1000 dados jogados
» milDados = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0}
» for t in range(1000):
»     milDados[random.choice([1,2,3,4,5,6])] += 1
» for t in range(1,7,2):
»     print('Lado {}: {} \t Lado {}: {}'.format(t, milDados[t], t+1, milDados[t+1]))
↳ Lado 1: 178 	 Lado 2: 171
↳ Lado 3: 160 	 Lado 4: 166
↳ Lado 5: 158 	 Lado 6: 167

» # o método sample(lista, n) escolhe n elementos da lista
» lista = [i for i in range(10,100,5)]
» print(lista)
↳ [10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
» print(random.sample(lista, 4))
↳ [20, 90, 95, 55]

» lista = ['banana', 'laranja', 'abacaxi', 'uva', 'pera']
» random.sample(lista, 2)
↳ ['abacaxi', 'banana']

randrange(inicio, fim, passo) retorna um aleatório entre inicio (inclusive) e fim (exclusive) com passos de passo, um parâmetro opcional com default de passo = 1. Todos os parâmetros são inteiros.

uniform(inicio, fim) retorna um float aleatório entre inicio e fim (ambos inclusive). Os parâmetros podem ser floats.

» # randrange(5, 10, 2) só pode retornar 5, 7 ou 9
» print(randrange(5, 10, 2))
↳ 9

» for t in range(5):
»     print(random.randrange(0, 4), end=' ')
↳ 0 3 3 1 2     

» # uniform lida com floats
» for t in range (5):
»     print(random.uniform(3.14, 3.16), end=' ')
↳ 3.1561382140695495 3.1525027738382234 3.152682503779535 3.1532559087053946 3.1411872204770708     

Aviso importante: Os pseudo geradores desse módulo não devem ser usados para gerar números (pseudo) aleatórios para fins de segurança, tais como a geração de chaves de criptografia, gerenciamento de segurança de passwords e autenticação e tokens de segurança. Para essa finalidade use o módulo secrets.

Datas e calendários

Para trabalhar com datas podemos importar o módulo datetime.

Uma das classes definidas nesse módulo é datetime que tem os métodos datetime.now(), a data e hora do momento, e e datetime.today(), a data apenas, ambas de acordo com o ajuste do computador. Um objeto de data contém o ano (year), mês (month), dia (day), hora (hour), minuto (minute), segundo (second), e microsegundo (microsecond).

O módulo datetime contém as classes date, time, datetime e timedelta.

» import datetime as dt

» # datetime.now() retorna a data e hora do momento
» d = dt.datetime.now()
» print(d)
↳ 2021-06-05 16:27:46.444763

» print('%d/%d/%d' % ( x.day, x.month, d.year))
↳ 5/6/2021

» # usando o método format de strings
» print('Ano: {}, Mẽs: {}, Dia: {}'.format(d.day, d.month, d.year))
↳ Ano: 7, Mẽs: 6, Dia: 2021

# ou
» print('{d.day}/{d.month}/{d.year}'.format(d=d))
↳ 7/6/2021

» print('minutos: %d, segundos:%d,%d' % ( d.minute, d.second, d.microsecond))
↳ minutos: 27, segundos:46,444763

» # usando o método format de strings:
» print('minutos: {}, segundos:{},{}'.format(d.minute, d.second, d.microsecond))
↳ minutos: 1, segundos:56,516050

» # ou
» print('{d.minute}min:{d.second},{d.microsecond}s'.format(d=d))
↳ 1min:56,516050s

Para inicializar uma variável com um valor qualquer usamos o construtor da classe datetime. Horas, minutos e seguntos, se não fornecidos, são ajustados para zero por default.

Uma data pode ser criada à partir um timestamp, que é o número de segundos decorridos desde 01 de janeiro de 1970 (UTC), usando o método fromtimestamp().

» # inicializando uma data
» d2 = dt.datetime(1996, 6, 14)
» print(d2)
↳ 1996-06-14 00:00:00

» # data de um timestamp
» ttp = dt.datetime.fromtimestamp(1613000000)
» print(ttp)
↳ 2021-02-10 20:33:20

Para conseguir uma formatação mais minuciosa de datas e hotas podemos usar o método strftime que converte um data em string de acordo com alguns parâmetros de valores pré-estabelecidos. Objetos das classes date, datetime e time todos possuem o método strftime(padrao) para criar uma representação de string representando seu valor.

Por outro lado o método datetime.strptime() constroi um objeto datetime partindo de uma string, representada em um padrão.

» d = dt.datetime.now()
» print(d.strftime('%d/%m/%y'))
↳ 07/06/21

» print(d.strftime('%d/%m/%Y')) 
↳ 07/06/2021

» print(d.strftime('%b'))   # sistema em inglês
↳ Jun

» print(d.strftime('%A, %d de %B, dia %j do ano de %Y')) 
↳ Monday, 07 de June, dia 158 do ano de 2021

A representação de horas segue o mesmo padrão:

» from datetime import time
» # time(hour = 0, minute = 0, second = 0) default
» a = time()
» print('a =', a)
↳ a = 00:00:00

» # time(hour, minute and second)
» b = time(8, 14, 15)
» print('b =', b)
↳ b = 08:14:15

» # time(hour, minute and second)
» c = time(hour = 8, minute = 14, second = 15)
» print('c =', c)
↳ c = 08:14:15

» # time(hour, minute, second, microsecond)
» d = time(8, 14, 15, 34567)
» print('d =', d)
↳ d = 08:14:15.034567

» print(b.strftime('Hora: %I%p, Minuto:%M, Segundo: %S '))
↳ Hora: 08AM, Minuto:14, Segundo: 15 

A diferença entre datas, calculadas com objetos datetime ou date é um objeto timedelta. O número de segundos em um intervalo é dado pelo método total_seconds().

» # diferença entre datas feitas entre objetos datetime e date 
» from datetime import datetime, date

» t1 = date(year = 2021, month = 6, day = 7)
» t2 = date(year = 1957, month = 6, day = 28)
» t3 = t1 - t2
» print('Diferença entre t1 e t2 =', t1 - t2)
↳ Diferença entre t1 e t2 = 23355 days, 0:00:00

» t4 = datetime(year = 2018, month = 7, day = 12, hour = 3, minute = 19, second = 3)
» t5 = datetime(year = 2021, month = 2, day = 12, hour = 1, minute = 15, second = 1)
» print('Diferença entre t4 e t5 =', t4 - t5)
↳ Diferença entre t4 e t5 = -946 days, 2:04:02

» print("type of t3 =", type(t3))
↳ type of t3 = <class 'datetime.timedelta'>
» print("type of t6 =", type(t6))
↳ type of t3 = <class 'datetime.timedelta'>

A diferença entre datas e horas pode ser calculada diretamente entre objetos timedelta.

» from datetime import timedelta
» t1 = timedelta(weeks = 2, days = 5, hours = 1, seconds = 33)
» t2 = timedelta(days = 4, hours = 11, minutes = 4, seconds = 54)
» deltaT = t1 - t2
» print("Diferença: ", deltaT)
↳ Diferença:  14 days, 13:55:39

» print("Segundos decorridos =", deltaT.total_seconds())

O método datetime.strptime(formato, data) tem o efeito oposto. Dada uma string contendo uma data escrita de acordo com formato ele retorna a data correspondente.

» from datetime import datetime
» data_string = '21 jun, 18'
» data1= datetime.strptime(data_string, '%d %b, %y')
» print('data =', data1)
↳ data = 2018-06-21 00:00:00

» data_string = '21 June, 2018'
» data2 = datetime.strptime(data_string, '%d %B, %Y')
» print('data =', data2)        
↳ data = 2018-06-21 00:00:00

Abaixo uma lista com os valores de parâmetros de formatação de strftime e strptime. Os exemplos são para a data datetime.datetime(2013, 9, 30, 7, 6, 5).

%Znome do fuso horário.,%jdia do ano, número preenchido com zero.273,
%-jdia do ano, número.(p) 273,%Unúmero da semana no ano (domingo é dia 1), número preenchido com zero.39,%Wnúmero da semana do ano (segunda-feira é dia 1), número.39,%crepresentação completa de data e hora (l).Seg, 30 de setembro 07:06:05 2013,%xrepresentação completa de data apenas (l).30/09/13,%Xrepresentação completa de tempo (l).07:06:05,%% o caracter literal ‘%‘.

Código Significado Exemplo
%a dia da semana abreviado (l). seg,
%A dia da semana completo (l). Segunda-feira,
%w dia da semana, numerado: 0 é domingo e 6 é sábado. 1,
%d dia do mês preenchido com zero. 30,
%-d dia do mês como um número decimal. (p) 30,
%b nome do mês abreviado (l). Set,
%B nome do mês, completo (l). setembro,
%m número do mês preenchido com zero. 09,
%-m número do mês. (p) 9,
%y ano sem século como um número decimal preenchido com zero. 13,
%Y ano com século como um número decimal. 2013,
%H hora (em 24 horas) como um número decimal preenchido com zero. 07,
%-Hv hora (em 24 horas) como um número decimal. (p) 7,
%I hora (em 12 horas) como um número decimal preenchido com zero. 07,
%-I hora (em 12 horas) como um número decimal. (p) 7,
%p AM ou PM (l). AM,
%M minuto, número preenchido com zero. 06,
%-M minuto, número. (p) 6,
%S segundos, número preenchido com zero. 05,
%-S segundo como um número decimal. (p) 5,
%f microssegundo, número preenchido com zeros à esquerda. 000000,
%z deslocamento UTC no formato + HHMM ou -HHMM.

Códigos marcados com (l) tem resultado dependente do ajuste local para datas. Os marcados com (p) são variáveis de acordo com a a plataforma.

Módulo timeit

O módulo timeit contém métodos úteis para medir o tempo decorrido na execução de um bloco de código. Diferentes modos de implementação de um código podem ser executados em tempos muito diferentes e medir esse tempo, em execuções mais longas, pode ser uma ótima forma de decidir por um otimização.

O método principal é
timeit.timeit(stmt=’codigo1′, setup=’codigo2′, timer=<timer>, number=1000000, globals=None)
onde codigo1 é uma string com as linhas de cógigo cujo tempo de execução se quer medir, codigo2 é string com o cógigo a ser executado previamente, <timer> é o timer a ser usado, number é o número de repetições da execução, e globals especifica o namespace onde será rodado o código.

No exemplo abaixo timeit.timeit('123456 + 654321', number=100_000) mede o tempo de execução da soma 123456 + 654321, repetida 100_000 vezes. O resultado da medida é retornado em segundos, no caso de t = 8.7 x 10-9 seg.

» import timeit
» timeit.timeit('123456 + 654321', number = 100_000)
↳ 0.000865324996993877
» timeit.timeit('123456 + 654321', number = 1_000_000)
↳ 0.008846134001942119

» # o código é realmente executado (evite outputs extensos)
» timeit.timeit('print("-", end=" ")', number = 10)
↳ - - - - - - - - - - 
↳ 0.0006212080006662291

Lembrando, podemos usar o underline como separador de milhares, apenas para facilitar a leitura de números longos. Vemos que realizar a mesma soma 10 vezes mais aumento o tempo de execução.

Suponha que queremos comparar o tempo de construção de uma tupla e uma lista usando o mesmo procedimento de listas de compreensão.

» # lista e tupla com inteiros de 0 a 1000, apenas múltiplos de 5
» lista = [i for i in range(1000) if i%5==0]
» tupla = (i for i in range(1000) if i%5==0)
» # qual deles é mais rápido?

» codigo1 = '''
» lista = [i for i in range(1000) if i%5==0]
» '''
» codigo2 = '''
» tupla = (i for i in range(1000) if i%5==0)
» '''
» # medimos o tempo de construção desses 2 objetos
» t1 = timeit.timeit(codigo1, number = 1000)
» print(t1)
↳ 0.07810732900179573

» t2 = timeit.timeit(codigo2, number = 1000)
» print(t2)
↳ 0.000657964003039524

» # comparando
» print('t1 = {} t2'.format(t1/t2))
» t1 = 118.71064167792143 t2

Vemos pelo último teste que a construção da tupla é mais de 100 vezes mais rápida que a da lista.

O tempo medido varia entre experimentos diferentes pois depende de uma série de fatores tais como quais os processos o computador já tem em execução e qual tempo está disponivel para o processamento do Python. É claro que varia também para máquinas diversas com velocidades de processador e memória disponível diferentes.

Qualquer bloco de código pode ser inserido para parâmetro de timeit, transformado em uma string. Também podemos passar funções como parâmetro.

» timeit.timeit('''
» import math
» listaFat = []
» def fatoriais(n):
»     for t in range(n):
»         listaFat.append(math.factorial(t))
»     print(listaFat)
» ''', number = 1_000_000)
↳ 0.28315910099991015

» # passando funções como parâmetros
» # soma dos 1000 primeiros numeros

» def soma1():
»     soma=0
»     cont=0
»     while cont ≤ 1000:
»         soma +=cont
»         cont +=1

» def soma2():
»     soma=0
»     for i in range(1001):
»         soma +=i

» timeit.timeit(soma1, number = 10000)
↳ 0.9234309990024485

» timeit.timeit(soma2, number = 10000)
↳ 0.6420390400016913

» import math
» def soma3():
»     soma = math.fsum(range(1001))    

» timeit.timeit(soma3, number = 10000)
↳ 0.2940528209983313

Vemos que usar range() para gerar uma sequência é mais rápido do que somar um contador um a um. O método math.fsum() é otimizado para velocidade e roda mais rápido de que as operações anteriores.

No exemplo abaixo um bloco de código é executado antes do código cuja execução é medida. Ele é usado para inicializar variáveis e preparar um cabeçalho para a exibição do resultado e seu tempo de execução não é incluído na medida.

» pre='a=10;conta=0;print("Conta |Valor de a");print("----------------")'
» cod='''
» for t in range(100):
»     conta +=1
»     a+=t
» print("{}   |  {}".format(conta, a))
» '''
» t = timeit.timeit(stmt = cod, setup = pre, number=5)
» print('O tempo de execução do código foi de \nt = {} seg'.format(t))
↳ Conta |Valor de a
↳ ----------------
↳ 100   |  4960
↳ 200   |  9910
↳ 300   |  14860
↳ 400   |  19810
↳ 500   |  24760
↳ O tempo de execução do código foi de 
↳ t = 0.0005647910002153367 seg

timeit no Jupyter Notebook


No Jupyter Notebook existe um método mágico para aplicar timeit a um bloco de código sem necessidade de se importar o módulo. Usamos %timeit comando para medir o tempo de execução do comando, e %%timeit célula para medir o tempo de execução em toda a célula.

No modo de linha várias comandos podem ser separados por ponto e vírgula, (;). Assim como na execução com timeit importado, como várias rodadas de execução do código são medidas, o código é efetivamente executado e, por isso, se deve evitar inserir partes com outputs extensos.

» %timeit r = range(10)
↳ 199 ns ± 9.94 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

» %timeit r = range(1000)
↳ 270 ns ± 18.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

» # o código é avaliado em namespace separado
» print(r)
↳ NameError: name 'r' is not defined

» # comandos alinhados com ;
» %timeit import math; n=10; m=100; math.pow(n,m)
↳ 303 ns ± 16.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

» # exemplo de exibição longa:
» %timeit for t in range(100): print(t)

» # 81125 linhas são exibidas

Para medida do tempo de execução de uma célula inteira usamos %%timeit.

» def fatorial(n):
»     if n <= 1: return 1
»     else: return n*fatorial(n-1)

-----------------------------------(início da célula)-------
» %%timeit
» n = 10
» fatorial(n)
-----------------------------------(  fim da célula )-------
↳ 1.54 µs ± 77 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

-----------------------------------(início da célula)-------
» %%timeit
» import math
» n=10
» math.factorial(n)
-----------------------------------(  fim da célula )-------
↳ 250 ns ± 8.43 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

» # em linha
» %timeit math.factorial(10)
↳ 110 ns ± 2.85 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)    	

Para medir o tempo de execução de um bloco de código também podemos usar o método timeit.default_timer() para obter um marco de tempo antes e após sua execução.

» import timeit
» import math

» def funcao_1():
»     soma = 0
»     t = 0
»     while t < 1_000_000:
»         soma += t
»         t += 1
»     return soma

» def funcao_2():
»     nums = [i for i in range(1_000_000)]
»     return math.fsum(nums)
    
» inicio = timeit.default_timer()
» funcao_1()
» intervalo_1 = timeit.default_timer() - inicio

» inicio = timeit.default_timer()
» funcao_2()
» intervalo_2 = timeit.default_timer() - inicio

» print('A função 1 demorou {} seg'.format(intervalo_1))
» print('A função 2 demorou {} seg'.format(intervalo_2))
↳ A função 1 demorou 0.12041138499989756 seg
↳ A função 2 demorou 0.07828983699982928 seg

O método timeit admite as seguintes opções:
Em modo de linha:
%timeit [-n N -r R [-t|-c] -q -p P -o]
Em modo de célula:
%%timeit [-n N -r R [-t|-c] -q -p P -o]
onde as opções são:

opção descrição
-n n executa o bloco n vezes em cada loop. Se não fornecido um valor de ajuste é escolhido,
-r r repete o loop r vezes e toma o melhor resultado. Default: r = 3,
-t usa time.time para medir o tempo, que é o default no Unix,
-c usa time.clock para medir o tempo, que é o default no Windows,
-p p usa precisão de p dígitos para exibir o resultado. Default: p = 3,
-q modo silencioso, não imprime o resultado,
-o retorna TimeitResult que pode ser atribuído a uma variável.

O módulo timeit é considerado mais preciso que time, descrito a seguir. Ele executa, por default, o bloco de código 1 milhão de vezes, retornando o menor tempo de execução. Esse número pode ser alterado com o parâmetro number.

Módulo time

O módulo time possui o método time.time() que retorna o momento de sua execução como um timestamp do Unix. The ctime() transforma esse timestamp em um formato padrão de ano, mês, dia e hora.

» import time
» t = time.time()
» print('Tempo decorrido desde a "época" {} segundos'.format(t))
↳ Tempo decorrido desde a "época" 1625697356.5482924 segundos

» horaLocal = time.ctime(t)
» print("Hora local: {}".format(horaLocal))
↳ Hora local: Wed Jul  7 19:35:56 2021

O método time.sleep(n) pausa a execução do código por n segundos, onde n pode ser um inteiro ou float.

» import time
» ti = time.time()
» time.sleep(3.5)
» tf = time.time()

» print('Pausa de {delta:.2f} segundos'.format(delta=tf - ti))
↳ Pausa de 3.50 segundos

Também existe, no Jupiter Notebook, o método mágico %time que mede o tempo decorrido na execução de uma função, similar ao comando time do Unix. Diferentemente de %timeit esse método também exibe o resultado do cálculo.

-----------------------------------(início da célula)-------	
» %time sum(range(100000))
-----------------------------------(  fim da célula )-------
↳ CPU times: user 2.1 ms, sys: 0 ns, total: 2.1 ms
↳ Wall time: 2.1 ms
↳ 4999950000

-----------------------------------(início da célula)-------
» %%time
» soma = 0
» for t in range(1000):
»     soma+=t
» soma
-----------------------------------(  fim  da célula)-------
↳ CPU times: user 150 µs, sys: 7 µs, total: 157 µs
↳ Wall time: 160 µs
↳ 499500 

» # w está disponível após a operação
» %time w = [u for u in range(1000)]
↳ CPU times: user 36 µs, sys: 8 µs, total: 44 µs
↳ Wall time: 47 µs

» print(w)
↳ [0,1,2,...,999]

Módulo string

O Python é notoriamente bom para o gerenciamento de texto. Uma das extensões dessas funcionalidades está no módulo string que contém uma única função e duas classes. A função é capwords(s, sep=None) que parte a string usando str.split() no separador sep; em seguida ela torna o primeiro caracter de cada parte em maiúsculo, usando str.capitalize(); finalmente junta as partes com str.join(). O separador default é sep=’ ‘ (espaço).

» # usando separador default    
» frase = 'nem tudo que reluz é ouro'
» maiuscula = string.capwords(frase)
» print(maiuscula)
↳ Nem Tudo Que Reluz É Ouro

# usando separador '\n' (quebra de linha)
» frase = 'mais vale um pássaro na mão\nque dois voando'
» maiuscula = string.capwords(frase, sep='\n')
» print(maiuscula)
↳ Mais vale um pássaro na mão
↳ Que dois voando    

Um número de constantes são carregadas com o módulo. Elas são especialmente úteis no tratamento de texto, por exemplo quando se deseja remover toda a pontuação de um texto base.

» # string module constants
» print('1.', string.ascii_letters)
» print('2.', string.ascii_lowercase)
» print('3.', string.ascii_uppercase)
» print('4.', string.digits)
» print('5.', string.hexdigits)
» print('6.', string.whitespace)  # ' \t\n\r\x0b\x0c'
» print('7.', string.punctuation)
↳ 1. abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
↳ 2. abcdefghijklmnopqrstuvwxyz
↳ 3. ABCDEFGHIJKLMNOPQRSTUVWXYZ
↳ 4. 0123456789
↳ 5. 0123456789abcdefABCDEF
↳ 6. 
↳ 
↳ 7. !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

Um exemplo simples de remoção de pontuação de um texto.

» # removendo pontuação de um texto
» txtBasico = 'Partimos de um texto com pontuação, queremos remover essas marcações!'
» pontuacao = string.punctuation
» filtrado =''
» for t in txtBasico:
»     if not t in pontuacao:
»         filtrado += t
» print(filtrado)
↳ Partimos de um texto com pontuação queremos remover essas marcações

O módulo string possui duas classes: Formatter e Template.

Formatter funciona da mesma forma que a função str.format(). A utilidade dessa classe está na possibilidade de derivar dela subclasses para a definição customizada de formatações.

» from string import Formatter
» formatador = Formatter()

» print(formatador.format('{} {}.{}', 'Recomendo o site','phylos', 'net'))
↳ Recomendo o site phylos.net

» print(formatador.format('{site}', site='phylos.net'))
↳ phylos.net

» print(formatador.format('{} {site}', 'Visite o site', site='phylos.net'))
↳ Visite o site phylos.net

» # A função format() tem o mesmo comportamento
» print('{} {website}'.format('Visite o site', website='phylos.net'))
↳ Visite o site phylos.net

A classe Templater é usada para criar templates para substituições em strings. Essa funcionalidade é útil, por exemplo, na criação de aplicativos internacionalizados (contendo mais de uma língua como opção na interface).

» from string import Template
» t = Template('$txt $sobrenome, $nome $sobrenome!')
» ing = t.substitute(txt='My name is', nome='James', sobrenome='Bond')
» pt = t.substitute(txt='Meu nome é', nome='Quim', sobrenome='Joah')

» print(ing)
↳ My name is Bond, James Bond!

» print(pt)
↳ Meu nome é Quim, Joah Quim!
Constantes de Strings
Constante Significado
string.ascii_letters Concatenação de ascii_lowercase e ascii_uppercase descritos abaixo,
string.ascii_lowercase Caracteres minúsculos ‘abcdefghijklmnopqrstuvwxyz’,
string.ascii_uppercase Caracteres maiúsculos ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’,
string.digits Dígitos: a string ‘0123456789’,
string.hexdigits Dígitos hexadecimais: a string ‘0123456789abcdefABCDEF’,
string.octdigits Dígitos octais: string ‘01234567’,
string.punctuation Caracteres ASCII considerados pontuação local: !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~,
string.printable Caracteres ASCII imprimíveis: digits, ascii_letters, punctuation, e whitespace,
string.whitespace String com todos os caracteres ASCII impressos como espaços em branco (space, tab, linefeed, return, formfeed, vertical tab).
🔺Início do artigo

Bibliografia

Consulte a bibliografia no final do primeiro artigo dessa série.