{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O NumPy é um pacote para realizar cálculos numéricos com facilidade e eficiência. Ele é tradicionalmente importado com o nome `np`, como no comando abaixo." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A principal estrutura de dados do NumPy é o *array*, que representa um array similar aos de C, mas com diversas operações simplificadas, como veremos.\n", "\n", "Uma forma de criar arrays é criando-os inicialmente com todos os elementos nulos ou todos os elementos unitários." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.zeros(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.ones(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note que o array criado tem valores de ponto flutuante; na verdade, de precisão dupla. Podemos especificar o tipo desejado dos elementos, se queremos algo diferente." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.zeros(10, dtype=np.int)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Outra forma de criar um array é convertendo uma lista Python em um array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "potencias_10 = np.array([1, 10, 100, 1000, 10000])\n", "potencias_10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arrays podem ser indexados da mesma forma que listas." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "potencias_10[2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "potencias_10[0:4:2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Outras formas úteis de criar arrays permitem gerar valores consecutivos. Por exemplo, temos uma generalização do `range` de Python, mas que aceita valores de ponto flutuante e retorna um array:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.arange(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.arange(1, 10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.arange(1, 10, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.arange(1, 10, 1.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Outra opção similar é especificar o número de intervalos desejados entre o início e o final, ao invés de especificar a separação entre os valores (como acontece no `arange`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.linspace(1, 10, 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note que no `linspace` o extremo superior é incluido." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.linspace(0, 10, 10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.linspace(0, 10, 10+1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos conserguir algumas informações sobre um array acessando alguns campos, como abaixo." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Chamamos de shape a descrição do número de elementos em cada dimensão do array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "potencias_10.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arrays podem ser criados com mais do que uma dimensão. Para isso, passamos uma tupla com os tamanhos nas diversas dimensões." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "m0 = np.zeros((5, 10))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mais tarde podemos consultar o shape para saber o formato do array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m0.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "m3 = np.ones((2, 2, 3))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m3.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Também podemos verificar qual o tipo de cada um dos elementos do array." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "m3.dtype" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "zzeros = np.ones(10, dtype=np.complex)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "zzeros" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "zzeros.dtype" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O tamanho de um array é o número total de elementos, considerando todas as dimensões." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m3.size" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "potencias_10.size" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Uma das coisas que fazem com que os arrays permitam uma programação simples é que podemos realizar operações algébricas sobre um array, o que significa realizar essas operações sobre cada elemento do array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "2 * np.ones(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "dois = 2 * np.ones(10)\n", "meio = 0.5 * np.ones(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dois - meio" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dois * meio" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dois / meio" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "3 * dois - meio / 0.1 + 4" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "3 * np.arange(10) - np.ones(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O NumPy também define diversas funções matemáticas que operam sobre arrays aplicando a função a cada um dos elementos do array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.exp(np.arange(10))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.exp(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "np.sin(np.linspace(0, 2*np.pi,10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Também é fornecido um módulo de geração de números pseudo-aleatórios:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.random(10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.random((4,4))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Algumas funções especiais sobre arrays:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.sum(np.ones(10))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.sum(np.arange(10))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sum(np.ones(10))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.prod(np.arange(1,10))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "alea = np.random.random(100)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.min(alea)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.max(alea)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "min(alea), max(alea)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.cumsum(np.arange(10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`cumsum` retorna um array que no índice `i` tem a soma dos valores do array original dos índices 0 até `i`.\n", "\n", "Vejamos agora algumas formas de indexar arrays." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sequencia = 10 * np.arange(100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Eles podem ser indexados como listas de Python." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia[2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia[1:4]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia[10:-10]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arrays multidimensionais são indexados usando uma tupla como índice." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mat = np.array([[1, 2, 3], [10, 20, 30]])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mat.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mat" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mat[0, 2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mat[1, 1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Usando slices em algum dos índices, pegamos todos os valores de índice correspondentes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mat[:, 2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "mat[:, 0:2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Também podemos fornecer um array de índices:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "ind = np.arange(10, 90, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ind" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "sequencia[ind]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ou uma lista de índices (que será convertida para um array)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia[[4, 7]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Um fator importante a considerar é que quando indexamos um array, o valor retornado não é uma cópia dos valores do array original (como ocorre no caso de listas), mas sim o que é chamado de uma nova **visão** (*view*) dos elementos indexados.\n", "\n", "Isso quer dizer que podemos alterar os elementos indexados diretamente." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sequencia[ind] = -sequencia[ind]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "sequencia[ind] = -1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ainda uma opção bastante útil é usar um array the booleanos para indexar. Neste caso, o array the booleanos deve ter o mesmo tamanho do array indexado, e serão escolhidos os elementos para os quais o índice for `True`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alguns = np.zeros_like(sequencia, dtype=np.bool) # em bool, 0 é False, 1 é True\n", "alguns[3:10] = True\n", "alguns[15:21] = True\n", "alguns[80:90] = True\n", "alguns" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia[alguns]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Uma das grandes utilidades disso é porque um array the booleanos é retornado quando fazemos comparação de arrays:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia < 0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia[sequencia < 0] = 0\n", "# sequencia < 0 retorna um array de booleanos com True apenas nos elementos negativos\n", "# sequencia[sequencia < 0] é o sub-array dos valores negativos de sequencia\n", "# A todos os elementos desse sub-array, atribuimos 0\n", "# Resultado final: os valores negativos de sequencia são zerados.\n", "sequencia" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = np.array([[1, -2, 0], [-1, 2, 0]])\n", "m" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m < 0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[m<0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos também gerar **outra visão** de um array simplesmente encarando os mesmos valores como se fossem um array de outras dimensões." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "ret = sequencia.reshape((10, 10))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia.size, ret.size" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sequencia.shape, ret.shape" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ret" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Como essa é uma outra visão, se alteramos o array `ret` estamos também alterando o array orginal `sequencia`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "ret[9, 9] = 9999" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ret" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "sequencia" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos também construir um array juntando pedaços de outros arrays." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "p1 = np.zeros(8)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "p2 = np.ones(8)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.concatenate((p1, p2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.concatenate((p1.reshape((8,1)), p2.reshape(8,1)), axis=1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.concatenate((p1.reshape((1,8)), p2.reshape(1,8)), axis=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O parâmetros `axis` especifica a dimensão a usar para fazer a concatenação (0: linha, 1: colunas, etc).\n", "\n", "Também podemos separar um array em diversos pedaços." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a, b = np.split(np.arange(20), 2) # Separa em duas partes, uma vai para a outra para b" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Existem diversas formas de realizar o `split`, dependendo dos parâmetros passados. Procure a documentação na Internet para mais detalhes. \n", "\n", "O `split` também gera um **view**, ao invés de arrays novos." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c = np.arange(20)\n", "a, b = np.split(c, 2)\n", "a[0] = 100\n", "b[0] = 200\n", "c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ao lidar com array, os arrays de 1 dimensão são bastante versáteis e úteis, porém temos que tomar cuidado quando estamos fazendo cálculos matriciais, pois neste caso termos vetores linha (que correspondem a uma matriz $N \\times 1$) e vetores coluna (que correspondem a uma matriz $1 \\times N$). **Os arrays unidimensionais de NumPy não correspondem a nenhum desses casos.**\n", "\n", "Minha recomendação é, quando realizando cálculos matriciais, utilizar sempre explicitamente arrays $N\\times 1$ ou $1\\times N$, conforme o apropriado.\n", "\n", "Por exemplo, o produto de matrizes é realizado pela função `dot` de NumPy:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "m1 = np.array([[1, 2], [3, 4]])\n", "m2 = np.array([[1, 0],[0, 1]])\n", "print(m1)\n", "print(m2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Este é o produto elemento a elemento:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1 * m2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Este é o produto de matrizes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.dot(m1, m2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "v1 = np.array([1, 2]).reshape((1, 2))\n", "v2 = np.array([1, 2]).reshape((2, 1))\n", "print('v1.shape=', v1.shape, 'v2.shape=', v2.shape)\n", "print('v1:\\n', v1)\n", "print('v2:\\n', v2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.dot(v1, m1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.dot(m1, v2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "np.dot(v1, v2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "v3 = np.array([1,2])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.dot(v3, m1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.dot(m1, v3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.dot(v3, v3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v3 * m1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1 * v3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v3 * v3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para o `numpy.dot`, as regras são as de multiplicação de matrizes. Para as outras operações, a explicação desses resultados é através das **regras de _broadcast_**.\n", "\n", "Dados dois arrays, as dimensões são alinhadas a partir da mais à direita. Os valores de tamanho em uma dimensão são compatíveis se eles são iguais ou se um deles é 1. Se o número de dimensões é diferente, o com menos dimensões tem seu número de dimensões expandido, colocando tamanhos 1 em cada nova dimensão. Dimensões de tamanho 1 são esticadas por cópia para o tamanho no outro array.\n", "\n", "Por exemplo, se operamos array $2\\times 3\\times 5$ com outro $3\\times 1$, os passos são os seguintes:\n", "- Ajusta à direita:\n", "```\n", " 2 3 5\n", " 3 1\n", "```\n", "- Aumenta o número de dimensões do menor, acrescentando um dimensão de tamanho 1.\n", "```\n", " 2 3 5\n", " 1 3 1\n", "```\n", "- Estica as dimensões 1 que correspondem a dimensões maiores no outro array, fazendo cópias.\n", "```\n", " 2 3 5\n", " 2 3 5\n", "```\n", "- Realiza as operações elemento a elemento." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a = 2 * np.arange(0, 30, 1).reshape((2, 3, 5))\n", "b = 3 * np.arange(1,4,1).reshape((3, 1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "b" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "a - b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Operar sobre todos os elementos de um array NumPy simultaneamente é bastante conveniente. Porisso, muitas vezes você vai querer implementar funções Python que operam dessa forma.\n", "\n", "Existem dois modos em que isso pode ser feito, adequados para situações diferentes. O primeiro caso é quando a nossa função realiza apenas operações já definidas sobre arrays NumPy da forma que desejamos. Por exemplo, suponha que temos uma função que calcula um polinômio:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def my_poly(x):\n", " return 2 * x**2 - 3 * x + 4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Essa função pode ser aplicada sobre escalares:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(my_poly(3))\n", "print(my_poly(5.1))\n", "print(my_poly(2+1j))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mas ela também pode ser aplicada diretamente a arrays NumPy:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_arr = np.array([1, 2, 3, 4])\n", "print(my_poly(my_arr))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Isto funciona pois a função `my_poly` usa apenas operações definidas para arrays NumPy: exponenciação com escalar, produto por escalar, subtração de arrays. Assim, quando chamamos `my_poly(my_arr)` a variável `x` de `my_poly` referenciará o mesmo array que `my_arr`, e estaremos fazendo\n", "\n", " 2 * my_arr**2 - 3 * my_arr + 4\n", " \n", "O que, como esperado, executará as diversas operações elemento a elemento.\n", "\n", "Infelizmente, isto não sempre funciona, falhando quando a função realiza alguma operação não definida para arrays NumPy. Por exemplo, suponha a função seguinte:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def region(x):\n", " if x < 2:\n", " return 0\n", " elif x < 4:\n", " return 1\n", " else:\n", " return 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Esta função funciona para escalares (que tem ordem total):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(region(3))\n", "print(region(5.1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mas ela não funciona para arrays:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(region(my_arr))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Novamente, isso ocorre porque o `x` da função `region` será uma referência para um array, e neste caso a comparação `x < 2` (por exemplo) no `if` retorna um array de booleanos, ao invés de um booleano, como esperado pelo Python.\n", "\n", "O que desejamos é que essa função seja aplicada individualmente a cada elemento do array. Isto é denominado em NumPy de **vetorização**. Para isto funcionar, precisamos soliciar ao NumPy que gere uma versão vetorizada da função, usando a função `np.vectorize`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "region = np.vectorize(region)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Agora podemos passar um array para essa função, e ela será chamada para cada um dos elementos:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(region(my_arr))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O que acontece é que, ao chamarmos `np.vectorize` para uma função criamos uma nova função que aplica a função original a cada um dos elementos do array passado, ao invés de passar o array diretamente para a função, como antes.\n", "\n", "Note que colocamos a nova função retornada por `np.vectorize` numa variável com o mesmo nome da função original. Isto não gera problemas, pois a função continua podendo ser aplicada a escalares, retornando um array de 1 elemento:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "region(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Se isso for inconveniente para o seu código, você pode colocar a função vetorizada em uma variável com nome diferente." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A principal razão para a existência de NumPy é permitir executar códigos numéricos com boa eficiência em Python. Então, para não deixar a questão de eficiência sem mencionar, vamos fazer um experimento simples: Calcular o cosseno de diversos ângulos igualmente espaçados entre 0 e $2\\pi$, usando Python puro e NumPy." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import math" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "N = 100000" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%timeit\n", "res = []\n", "for i in range(N+1):\n", " xi = 2 * i * math.pi/N\n", " res.append(math.cos(xi))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%%timeit \n", "[math.cos(2*i*math.pi/N) for i in range(N+1)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%timeit list(map(lambda i: math.cos(2*i*math.pi/N), range(N+1)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%timeit np.cos(np.linspace(0, 2*np.pi, N+1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.1" } }, "nbformat": 4, "nbformat_minor": 2 }