pip install numpy
pip install sympy
pip install PIL
pip install jupyter pip install matplotlib
1 Introdução ao Python
O programa Python foi escolhido para ministrar este curso por uma série de razões. Além de ser um programa livre, no sentido de possuir livre distribuição e código fonte aberto, pode ser utilizado nas plataformas Windows
e Linux
. Além do mais, o Python possui grande versatilidade no sentido de possuir inúmeros pacotes já prontos e nos possibilitar criar novas rotinas e funções. O PyPi
é o repositório oficial do Python onde todos os pacotes são armazenados. Você pode pensar nele como um Github para os pacotes do Python. O Python foi criado pelo holandês Guido van Rossum para ser uma linguagem de programação simples e legível, além de ser muito produtiva. O Python evoluiu e se tornou em uma linguagem muito atrativa e uma das principais escolhas para aplicações de desenvolvimento web, análise de dados e inteligência artificial, entre outras. Por ser genuinamente um programa orientado por objeto nos possibilita programar com muita eficiência e versatilidade, embora apresente algumas mudanças em sua implementação em relação a outras linguagens orientadas por objetos. Outro aspecto que é bastante atrativo no Python refere-se ao fato de o mesmo receber contribuições de pesquisadores de todo o mundo na forma de pacotes. Essa é uma característica que faz com que haja grande desenvolvimento do programa em relativamente curtos espaços de tempo e que nos possibilita encontrar soluções para quase todos os problemas com os quais nos deparamos em situações reais. Para os problemas que não conseguimos encontrar soluções, o ambiente de programação Python nos possibilita criar nossas próprias soluções.
Nestas notas de aulas pretendemos apresentar os conceitos básicos da estatística computacional de uma forma bastante simples. Inicialmente obteremos nossas próprias soluções para um determinado método ou técnica e em um segundo momento mostraremos que podemos ter a mesma solução pronta do Python quando esta estiver disponível. Particularmente neste capítulo vamos apresentar algumas características do ambiente e da linguagem para implementarmos nossas soluções. Nosso curso não pretende dar soluções avançadas e de eficiência máxima para os problemas que abordaremos, mas propiciar aos alunos um primeiro contato com a linguagem Python e com os problemas básicos da estatística computacional.
A desvantagem é que o Python não é um programa fácil de aprender. Alguns esforços iniciais são necessários até que consigamos obter algum benefício. Não temos a intenção de apresentar neste curso os recursos do Python para análises de modelos lineares de posto completo ou incompleto, de modelos não-lineares, de modelos lineares generalizados ou de gráficos. Eventualmente poderemos utilizar algumas destas funções como um passo intermediário da solução do problema que estaremos focando. Este material será construído com uma breve e simplificada abordagem teórica do tópico e associará exemplificações práticas dos recursos de programação Python para resolver algum problema formulado, em casos particulares da teoria estudada.
Este material é apenas uma primeira versão que deverá ter muitos defeitos. Assim, o leitor que encontrá-los ou tiver uma melhor solução para o problema poderá contribuir enviando um e-mail para danielff@ufla.br.
Visite minha homepage https://des.ufla.br/~danielff/.
1.1 Introdução aos Comandos e Objetos do Python
No Python os objetos podem ser de diferentes tipos ou estruturas, tais como os números (int
, float
e complexos), boolean
, string
, list
, tuple
, set
, dictionary
, functions
(objeto que encapsula códigos), dataframes
e muitos outros. Vamos descrever de forma sucinta e gradativa alguns destes objetos e comandos básicos do Python.
As instruções do Python podem ser escritas em um editor de texto e digitadas no terminal do programa. Para usarmos o Python, inicialmente instalamos uma distribuição do Programa. Recomendamos a versão atual (no momento do lançamento do livro) que pode ser baixada no site https://www.python.org/ do link https://www.python.org/ftp/python/3.13.0/python-3.13.0-amd64.exe. Para digitarmos os códigos, recomendamos que seja baixado o program livre Positron no site https://posit.co/ usando o link https://github.com/posit-dev/positron/releases/download/2024.11.0-140/Positron-2024.11.0-140-Setup.exe.
Veja um print do Positron:
Uma vez instalado, podemos digitar os códigos e com Ctrl Enter executamos os códigos linha por linha ou um bloco de linhas marcadas. É importante instalarmos algumas bibliotecas básicas, caso elas já não estejam instaladas. A seguir, temos um código Python para esse propósito.
Em seguida devemos importar as libraries que precisarmos. No script
a seguir consideramos a importação de todas as libraries. A library math
não precisa ser instalada, pois já vem com as distribuições do Python.
import math
import numpy
import sympy
import PIL
import jupyter
import matplotlib
1.1.1 Operações Aritméticas Básicas
Podemos usar o Python como uma calculadora, temos o seguinte programa, em que cada linha física do editor tem um comando Python específico. Podemos separar em uma mesma linha vários comandos com ponto e vírgula.
# Programa ilustrativo de operações elementares
# em Python
1 + 2 + 3 # soma dos 3 primeiros inteiros
3**10 - 1 + 8
6 / 5 + 0.5**4
6
59056
1.2625
Podemos observar que o símbolo #
é usado para inserirmos comentários no código Python e o operador /
faz divisão usando operadores reais. Para divisão de inteiros, podemos usar //
, assim, 6 // 5
retorna \(1\) e 6 / 5
retorna 1,2
. O resto da divisão por inteiro é obtido pelo operador %
. Assim, 6 % 5
retorna 1
. Temos também que o operador **
é a função potência, ou seja, por exemplo, \(3^{10}\) é 3**10
em Python.
O Python também pode realizar operações com números complexos, que no caso, são representados por \(a+bj\), em que \(a\) é a parte real do número e \(bj\), a parte imaginária, sendo \(j\) \(=\) \(\sqrt{-1}\). O \(j\) é representado por \(i\) nos livros de matemática e de outras áreas. O programa a seguir ilustra uma operação com números complexos dada por \((6-4i)^2\). Assim, temos
6-4j)**2 (
(20-48j)
Apresentamos a seguir um script
que faz uso da library math
, cujo primeiro comando foi para importá-la, o penúltimo para obter o valor de \(\pi\) e o último comando calculou \(\sqrt{2}\). Também fizemos mais uma operação com números complexos.
import math
2 + 4 + 5.6
2 / 3 - 4
3-4j)*(3+4j)
(
math.pi2) math.sqrt(
11.6
-3.3333333333333335
(25+0j)
3.141592653589793
1.4142135623730951
As bibliotecas numpy
e sympy
são para diversos cálculos matemáticos, sendo que a última efetua cálculos simbólicos.
import sympy
import numpy
='1.25')
numpy.set_printoptions(legacy/5)
sympy.sin(sympy.pi/5)
numpy.sin(numpy.pitype(1.5 + 2.1j) # tipo do objeto
\(\displaystyle \sqrt{\frac{5}{8} - \frac{\sqrt{5}}{8}}\)
0.5877852522924731
complex
Podemos realizar uma operação matemática básica como algumas das anteriormente apresentadas ou até mesmo, mais complexa e armazenar o valor em uma variável, digamos x
. Essa variável pode ser usada para outras operações matemáticas e até simbólicas, se usarmo o sympy
. Veja Knuth (1984) para discussão sobre programação simbólica. O script
a seguir ilustra alguns casos deste procedimento.
= sympy.symbols('y')
x = (1+x**2)**2
z
sympy.simplify(z)= 2.3 + 6.7**2
y = y**2 + 1 / 2**3
r print('r =', r, 'y = ', y, 'e', 'z =', z)
\(\displaystyle \left(y^{2} + 1\right)^{2}\)
r = 2227.0211 y = 47.19 e z = (y**2 + 1)**2
Assim, atribuímos dados às variáveis Python. O Python diferencia maiúsculas de minúsculas e nomes como X
e x
são diferentes. No Python as variáveis são ponteiros (pointers). Comandos como \(x = 2\) cria um objeto \(x\) e atribuí (armazena) o valor \(2\) nele em outras linguagens, mas no Python, há um objeto inteiro \(2\) e \(x\) é um ponteiro, apontando para ele. Veja as consequências disso a seguir, sendo que o comando \n
realiza uma quebra de linha. Não há maiores implicações em objetos escalares como este, mas quando se trata, por exemplo, de listas, nosso próximo objeto, a questão já é bem diferente.
= z = b = 1
x = 7
b print('x is pointing to', x,
'\nz is pointing to', z, '\nb is pointing to', b)
x is pointing to 1
z is pointing to 1
b is pointing to 7
Para lidarmos com funções de números complexos a library cmath
deve ser importada e as funções trigonométricas de números complexos podem ser usadas com base nesta biblioteca e não na library math
, que é designada para números reais (float
). Veja o script
ilustrativo a seguir.
import cmath
0.1 - 0.4j) + cmath.sin(0.2 + 0.6j))**2.5 (cmath.cos(
(1.0143106793360148+2.4164569923716543j)
1.2 Variáveis Booleanas
Variáveis booleanas em Python recebem os valores True
e False
apenas. Várias operações de comparações como ==
, >=
, <=
, &
(and), |
(or
) e !
(negação - not
) podem ser usadas para este tipo de objeto, as variáveis booleanas. Veja um simples exemplo disso. Posteriormente, voltaremos a falar destes operadores de variáveis booleanas.
= True
x
x= False
y
y!= y # ou
x = not(y)
z == z x
True
False
True
True
1.3 Strings
As strings (variáveis texto) são um importante tipo de objeto Python. Uma vez que temos um objeto definido, os métodos e funções estão disponíveis para serem usados. As strings são denotadas por str
em Python. Veja alguns exemplos, em que as strings foram atribuídas ou não a objetos (variáveis).
'Esta é uma string'
= 'Universidade Federal '
mensagem type(mensagem)
mensagem= mensagem + 'de Lavras, MG.'
UFLA UFLA
'Esta é uma string'
str
'Universidade Federal '
'Universidade Federal de Lavras, MG.'
Alguns métodos que podemos usar com as strings são ilustrados a seguir, entre muitos outros, mostrando o poder da linguagem orientada por objetos.
UFLA.capitalize()
UFLA.lower()
UFLA.upper() UFLA
'Universidade federal de lavras, mg.'
'universidade federal de lavras, mg.'
'UNIVERSIDADE FEDERAL DE LAVRAS, MG.'
'Universidade Federal de Lavras, MG.'
Estes métodos atuam no objeto, mas, como ficou claro no exemplo anterior, não mudam o conteúdo do objeto. Podemos realizar operações com strings, como ilustrado a seguir. O método str.format
possibilita formatar strings
, como, por exemplo, incluir substrings
nos campos marcados com {}
.
* 2
UFLA = 'Nome: {}, Sobrenome: {}'
nome format('Daniel', 'Furtado Ferreira') nome.
'Universidade Federal de Lavras, MG.Universidade Federal de Lavras, MG.'
'Nome: Daniel, Sobrenome: Furtado Ferreira'
Podemos usar o Python para interagir com o usuário, solicitando a entrada de dados (strings
no caso) com o comando input
. Veja os exemplos a seguir.
= input('entre com seu primeiro nome: ')
nome print(nome + ' foi aprovado!')
= int(input('entre com um valor inteiro: '))
x # transforma o str em inteiro: int
# se o número de entrada não for int, resulta em erro x
Se o usuário entrar com Daniel
, o resultado será Daniel foi aprovado!
. Esta versão de Markdown
ainda não suporta interatividade com o usuário. Portanto, o comando input
não foi avaliado na saída deste script
. No segundo comando, se o usuário entrar com um número não inteiro, haverá uma mensagem de erro do Python. Existem opções para lidar com erros deste tipo e de outras causas também.
1.4 Listas, Tuplas, Conjuntos e Dicionários
Vamos abordar cada um destes objetos separadamente. Vamos começar pelas listas.
1.4.1 Listas
As listas, lists
são os primeiros blocos de construção para lidarmos como manipulação de dados. As listas são vetores cujo primeiro elemento inicia-se no 0
, mas cujos elementos de cada célula pode ser diferentes tipos mistos, desde inteiros, booleanos, reais, complexos, strings, caracteres, conjuntos, tuplas e outras listas. As listas fazem parte do quarteto list
, tuple
, set
e dictionary
. A biblioteca numpy
fornece ferramentas adicionais para lidarmos com grande coleções de dados.
= [1, 2, 3, 4]
x
x= [1, 'Estat', 3.5, 4+5j]
y
ytype(x)
type(y)
3] y[
[1, 2, 3, 4]
[1, 'Estat', 3.5, (4+5j)]
list
list
(4+5j)
A variável x
é uma lista de inteiros com \(4\) elementos, que são indexados por \(0\), \(1\), \(2\), \(3\). Assim, \(x[1]\) aponta para o valor \(2\) e \(x[0]\) para o valor \(1\). A variável y
também é uma lista com \(4\) elementos de diferentes tipo, sendo \(y[0]\) um inteiro, \(y[1]\) uma string
\(y[2]\) um float
e \(y[3]\), um número complexo. Para criar a lista, simplesmente utilizamos as chaves []
, com cada elemento da lista separado por uma vírgula. É possível criar uma lista com elementos com valores repetidos e eles serão identificados como sendo diferentes, pois a lista respeita as ordens de entradas dos valores e preserva a ordem. Podemos verificar se um elemento pertence a lista com o comando in
, como mostra o script
a seguir, entre outros exemplos.
2, 7] == [7, 2]
[5, 7] == [5, 7, 7]
[
x2 in x
7 in x
y4+5j in y
'Daniel' in y
False
False
[1, 2, 3, 4]
True
False
[1, 'Estat', 3.5, (4+5j)]
True
False
Podemos, como foi feito com as strings
realizar algumas operações aritméticas com as listas, como mostra o exemplo do seguinte script
.
+y
x+[[0,1],'teste',[1,0]]
x*2
y0] # primeiro elemento da lista
y[-1] # último elemento da lista
y[# numeração -1,-2,-3,-4 para o índice
# acessa as posições, 3,2,1,0,
# respectivamente da lista y
[1, 2, 3, 4, 1, 'Estat', 3.5, (4+5j)]
[1, 2, 3, 4, [0, 1], 'teste', [1, 0]]
[1, 'Estat', 3.5, (4+5j), 1, 'Estat', 3.5, (4+5j)]
1
(4+5j)
Vejamos agora o problema dos ponteiros, por meio do exemplo do script
apresentado na sequência.
= z = b = [1,2,3]
x 1] = 7
b[print('x is pointing to', x,
'\nz is pointing to', z, '\nb is pointing to', b)
# todos os objetos foram alterados e não só b
# pois eles apontam para a mesma lista [1,2,3]
x is pointing to [1, 7, 3]
z is pointing to [1, 7, 3]
b is pointing to [1, 7, 3]
Observamos que se x
, z
e b
apontarem para o mesmo objeto, então se alterarmos o valor b[1]
de 2
para 7
, então todos os três objetos serão alterados na posição 1
, que corresponde ao segundo valor da lista, pois ela se inicia na posição 0
. Entretanto, se em vez de b[1] = 7
tivéssemos usado a atribuição b = [7,9]
, então os vetores x
e z
não seriam alterados, com a nova atribuição do vetor b
. Nos exemplos anteriores, vimos também que os elementos de uma lista são acessados pelo seu índice que varia de 0
a n-1
, sendo n
o seu tamanho. Assim, a lista x=[1,2,3,4]
tem seus elementos x[0]
igual a 1
, x[1]
igual a 2
, x[2]
igual a 3
e x[3]
igual a 4
. Também podemos variar o índice de -1
a -n
, sendo que -1
significa a última posição da lista, ou seja, a posição n-1
, -2
corresponde a posição n-2
e assim por diante até -n
, que corresponde a posição 0
da lista.
As listas são objetos e como tais podemos utilizar alguns métodos associados a eles. As listas são mutáveis e dinâmicas (podemos alterar seus elementos), são ordenadas (cada elemento da lista possui uma ordem definida na sua criação) e permitem elementos repetidos. Para usarmos um método ou uma função deveremos considerar a diferença entre eles. Embora todos métodos sejam funções em Python, nem toda função é um método. As funções recebem os objetos como entradas e não os modifica e os métodos agem nos objetos. A seguir apresentamos uma relação de alguns métodos ou funções associados às listas:
sort()
: ordena a lista em ordem crescente.append()
: adiciona um elemento ao final da lista.extend
: adiciona múltiplos elementos à lista.index()
: usado para encontrar o índice de um elemento na lista.max(list)
: retorna o valor máximo de uma lista.min(list)
: retorna o valor mínimo de uma lista.list(tuple)
: transforma umatuple
numa lista.len(list)
: retorna o tamanho da lista (número de elementos).filter(fun,list)
: filtra uma lista usando uma funçãofun
Python.
Vamos ilustrar alguns destes métodos com exemplos particulares. Vamos considerar uma lista e aplicarmos o método sort()
para ordenarmos os seus valores. Neste exemplo a seguir, vamos ver a diferença de um método e de uma função, observando como o método modifica o objeto que o chamou. Neste caso, a chamada de um método é dada pelo nome da lista (objeto) seguida de um ponto e do nome do método: lista.met()
.
= [7.4, 5.8, 9.3, 3.2]
x # objeto x original
x
x.sort()# objeto x modificado pelo método sort() ordenado x
[7.4, 5.8, 9.3, 3.2]
[3.2, 5.8, 7.4, 9.3]
Para o método append
temos o seguinte script
, que acrescentou o mês de Abril ao final de uma lista com os três primeiros meses do ano.
= ['Janeiro', 'Fevereiro', 'Março']
mes 'Abril')
mes.append(print(mes)
['Janeiro', 'Fevereiro', 'Março', 'Abril']
O método extend
é aplicado na lista x
anterior e acrescenta mais dois elementos ao final da mesma.
x1.6, 11.6])
x.extend([
x# ordena x, pois não estava mais em ordem
x.sort() x
[3.2, 5.8, 7.4, 9.3]
[3.2, 5.8, 7.4, 9.3, 1.6, 11.6]
[1.6, 3.2, 5.8, 7.4, 9.3, 11.6]
O index()
é um método para encontra o índice de um elemento na lista. Se o elemento procurado não estiver na lista, o Python mostrará uma mensagem de erro.
= x.index(7.4)
i # lembre-se que a lista começa no 0 e não no 1 i
3
Já as funções len
, max()
e min()
atuam no objeto, passado como entrada da função, mas não o modificam. Veja o exemplo na lista x
dos exemplos anteriores o efeito destas duas funções.
len(x) # tamanho da lista x
len(mes) # tamanho da lista mes
= max(x)
b = min(x)
a
a
b
mes# x e mes não modificados x
6
4
1.6
11.6
['Janeiro', 'Fevereiro', 'Março', 'Abril']
[1.6, 3.2, 5.8, 7.4, 9.3, 11.6]
Para ilustrar a uso da função filter()
vamos considerar uma função que retorna True
ou False
para uma certa condição de interesse. Por exemplo, se quiséssemos saber quais números dos seis elementos da lista x
possui resto da divisão por 2
menor que 1,5
. Esse resultado é obtido com a comparação a % 2 <= 1.5
, que irá retornar verdadeiro ou falso para o número representado por a
. Só que devemos fazer isso para todos os elementos da lista x
ou de outra lista qualquer. Devemos criar uma função para receber cada elemento da lista e verificar a condição, retornando True
ou False
e passar pela função filter()
para realizar a iteração nos elementos da lista x
ou na lista de interesse. Vamos criar um primeira função no exemplo a seguir e em seguida aplicar a função filter()
. O tipo de objeto retornado desta função é filter
, logo, tem de ser transformado em lista antes de imprimir.
def resto(a):
if ((a % 2) <= 1.5):
return True
else:
return False
# aplicar a função filter
= filter(resto, x)
x_filtrado print(list(x_filtrado))
[3.2, 7.4, 9.3]
Podemos usar a função list(objeto)
para construir uma lista a partir deste objeto, como ocorreu com o objeto x_filtrado
do script
anterior. Então a função list()
é um construtor de listas. Observe que o operador %
retorna o resto da divisão por inteiro, que no caso, foi por 2
. A função resto retorna verdadeiro ou falso, de acordo com a condição do resto da divisão de a
por 2
. Para definir uma função necessariamente usamos o comando def
seguido pelo nome da função (resto
) com o argumento (a
). Depois vem o corpo da função, que deve ter indentação obrigatória. Não há separação com {}
ou []
ou outros caracteres para separar o corpo da função. Esta separação é feita apenas com uso das indentações apropriadas. Falaremos posteriormente de função com mais detalhes.
Podemos aproveitar algumas funções prontas das listas para calcularmos algumas quantidades de interesse, como, por exemplo, a soma dos seus elementos. Para isso, poderíamos usar uma estrutura de repetição, como o for
que veremos posteriormente e na medida que o loop se adianta, vamos atualizando a soma. Porém podemos usar a função sum()
. Um loop for
é executado como bytecode Python interpretado, enquanto a função sum()
é escrita puramente na linguagem C, portanto, bem mais rápida e eficiente. Veja o código a seguir para ilustrarmos a soma de todos os valores do vetor x
. Falaremos das estruturas condicionais e de repetições posteriormente.
= sum(x)
soma
soma# média
/ len(x) soma
38.9
6.483333333333333
Outros métodos como o count()
(conta o número de ocorrências de um dado valor), reverse()
(ordena a lista em ordem reversa a ordem original), clear()
(limpa todos os dados da lista), copy()
(copia todos os dados da lista), insert()
(insere um elemento em uma posição específica da lista) e pop()
(remove um elemento em uma posição específica) como apresentado no scrip a seguir.
= [1, 2, 3, 5, 7, 2, 4.5]
L 2)
L.count(2.1)
L.count(= L.copy()
L1
L.reverse()
L
L.clear()
L
L11, 3) # insere o valor 3 na posição 1
L1.insert(
L11) # retira o elemento 3 da posição 1
L1.pop( L1
2
0
[4.5, 2, 7, 5, 3, 2, 1]
[]
[1, 2, 3, 5, 7, 2, 4.5]
[1, 3, 2, 3, 5, 7, 2, 4.5]
3
[1, 2, 3, 5, 7, 2, 4.5]
Podemos construir uma matriz, haja vista que Python não possui um objeto matricial, usando uma lista. Se criarmos uma lista de n
componentes, com cada um dos componentes tendo m
componentes, teremos uma matriz \(n \times m\). Vejamos no script
a seguir a construção da seguinte matriz:
\[ A = \left[ \begin{array}{cc} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{array} \right]. \qquad(1.1)\]
O script
correspondente a matriz definida em (Equation 1.1) é:
= [[1,4],[2,5],[3,6]]
A print('A = ',A)
A = [[1, 4], [2, 5], [3, 6]]
Vamos apresentar também alguns detalhes extras para acessarmos os valores de uma lista. Podemos acessar o valor de uma lista x
indicando a posição do elemento que queremos acessar da seguinte forma: x[i]
, em que i
representa um valor inteiro entre 0
e len(x)
, digamos n
. Para acessarmos um subconjunto de uma lista L
de tamanho n
, podemos usar o seguinte comando L[m:s]
que irá acessar os elementos da posição m
até a posição s-1
e não s
. Isso corresponde aos elementos m+1
, m+2
, ...
, s
. Assim não devemos confundir o elemento com sua posição, pois o vetor inicia-se em 0
(elemento 1
) e não em 1
. Se os índices são negativos, então a lista será acessada de trás para frente, sendo que -1
corresponde a posição n-1
, -2
a posição n-2
e assim sucessivamente até -n
, que corresponde a posição 0
. Veja alguns exemplos no script
a seguir.
= [1, 2, 3, 4, 5, 6, 7]
L L
[1, 2, 3, 4, 5, 6, 7]
Acessando posições em particulares, para impressão ou para atribuição:
0]
L[1] = 8
L[ L
1
[1, 8, 3, 4, 5, 6, 7]
Acessando, posições com os índices negativos.
-1] # posição n-1
L[-len(L)] # posição 0 L[
7
1
Para blocos de elementos temos que o comando L{m:s:r]
acessa os elementos nas posições m
, m+1+r
, m+1+2r
, ...
até a (s-1)
-ésima posição ou até a posição mais próxima de s-1
possível. Muito cuidado deve ser tomado, pois o limite, superior não indica onde o subconjunto termina entre os índices válidos de uma lista e sim,que ela termina na posição destacada subtraída de 1
.
1:4]
L[1:23] # passa do limite len(L)
L[0:5:2]
L[-1:-8:-1]
L[4:]
L[6] L[:
[8, 3, 4]
[8, 3, 4, 5, 6, 7]
[1, 3, 5]
[7, 6, 5, 4, 3, 8, 1]
[5, 6, 7]
[1, 8, 3, 4, 5, 6]
Se omitirmos os limites inferior ou superior da sequência, então a lista selecionada será iniciada no índice 0
(valor inicial) ou terminará no último índice (valor final da lista), como nos dois últimos exemplos apresentados.
1.4.2 Tuplas
As tuplas são objetos Python muito parecidos com as listas. Vários métodos e funções que se aplicam às listas também se aplicam às tuplas. Ao contrário das listas, as tuplas são objetos imutáveis, ou seja, uma vez criadas elas não podem ser modificadas. Assim, se criarmos uma tupla por t = (1,2,3)
não poderemos atribuir valor, por exemplo, deste jeito t[1] = 9
. Elas podem conter mais de um valor idêntico e são ordenadas, como as listas. A forma de criar a tupla em relação à lista é o uso dos parênteses no lugar dos colchetes.
= (1, 2, 'DFF', 3)
t 'DFF' in t
2]
t[0]
t[3]
t[:len(t)
True
'DFF'
1
(1, 2, 'DFF')
4
Os métodos count()
e index
podem ser usados nas tuplas, como ilustrado a seguir. O método index()
tem a seguinte sintaxe, sendo que os dois últimos argumentos são opcionais: tuple.index(element, start, end)
.
1) # número de ocorrência de 1
t.count('DFF') # índice da posição de 'DFF' t.index(
1
2
A tupla pode ter qualquer tipo como sendo seus elementos, incluindo uma tupla ou uma lista.
= ((1,2),'r', [2,3,4])
t1
t1print('Componente lista da tupla ',t1[2])
print('Elemento 0 do componente lista da tupla ',t1[2][0])
2].append(5)
t1[print('Modificando o componente lista da tupla ',t1)
= [1,2,3]
t2 sum(t2)
((1, 2), 'r', [2, 3, 4])
Componente lista da tupla [2, 3, 4]
Elemento 0 do componente lista da tupla 2
Modificando o componente lista da tupla ((1, 2), 'r', [2, 3, 4, 5])
6
A questão é: por que devemos usar tuplas, se elas não podem ser modificadas? A resposta para isso vem do fato de que a manipulação de dados via tuplas que são imutáveis é muito mais rápida do que nas listas. Apesar da tupla apontar para a mesma identificação da memória, fomos capazes de modificar um de seus elementos, que era a lista na sua segunda posição. Isso não mudou a identificação na memória para a qual a tupla t1
apontava.
Existem muitos métodos ou funções que funcionam com as tuplas como o len(t)
e o count()
como ilustrado a seguir.
= (1,1,2,3,3,3,4,5)
t 3)
t.count(len(t)
3
8
A função any(t)
retorna True
se há algum item True
na tupla e retorna False
, caso contrário. Neste caso, a tupla ou qualquer outro tipo apropriado poderá ter elementos 0
e 1
ou booleanos. Pode ser aplicada nas listas, conjuntos e nos dicionários.
= (True, False, False,False,True)
t any(t)
True
Podemos usar ainda as funções min()
, max()
, sum()
e sorted()
, como ilustrado a seguir. A função sorted()
ordena a tupla e retorna uma lista ordenada como resultado. Veja que o método lista.sort()
altera o objeto lista
e não pode ser aplicado na tupla, pelo fato de a tupla ser imutável.
= (2.3,4.5,1.2,1.1,9.7,5.3)
t min(t)
max(t)
sum(t)
sorted(t)
t
1.1
9.7
24.099999999999998
[1.1, 1.2, 2.3, 4.5, 5.3, 9.7]
(2.3, 4.5, 1.2, 1.1, 9.7, 5.3)
1.4.3 Conjuntos
Os conjuntos set
em Python tem uma conotação muito próxima com a definição de conjuntos da matemática. Esses são conjuntos que a ordem ou duplicação de seus elementos não mudam o conjunto. Assim, são imutáveis, não ordenados e não pode ter mais de um elemento idêntico em suas ocorrências. Podemos usar o construtor (função) set()
para criar um conjunto ou usarmos as chaves{}
para digitar seus elementos separados por vírgula.
= set()
phi print('Conjunto vazio: ', phi)
# precisa ser uma lista ou tupla de argumento
= set(['A','D','B','C','E'])
A
A= {1,2,3.4,5,6,6}
B 'A' in A
# repare que o elemento
B #repetido 6 aparece 1 vez apenas
0] B[
Conjunto vazio: set()
{'A', 'B', 'C', 'D', 'E'}
True
{1, 2, 3.4, 5, 6}
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[35], line 10 8 B # repare que o elemento 9 #repetido 6 aparece 1 vez apenas ---> 10 B[0] TypeError: 'set' object is not subscriptable
Não podemos acessar um elemento de um conjunto por B[0]
, por exemplo, o que ocasiona um erro, como pode ser visto no resultado do script
anterior. A ordem não é importante. Vejamos a comparação do conjunto A
anterior com o novo conjunto C
criado a seguir.
= set(['A','B','C','D','E'])
C
C== C A
{'A', 'B', 'C', 'D', 'E'}
True
Algumas operações matemáticas com conjuntos estão disponíveis em Python, como união, interseção, diferença (\(A^c \cap B\)) e diferença simétrica \(((A^c \cap B) \cup (A \cap B^c))\), como ilustrados no exemplo a seguir.
= {1,2,3,4,5,6}
A = {4,5,7,8,9,10}
B
A.union(B)
A.intersection(B)# esta em A, mas não em B
A.difference(B) # está em B, mas não em A
B.difference(A) # está só em A ou só em B
A.symmetric_difference(B) & B # intersecção
A | B # união
A - B # diferença
A - A # diferença
B ^B # diferença simétrica A
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
{4, 5}
{1, 2, 3, 6}
{7, 8, 9, 10}
{1, 2, 3, 6, 7, 8, 9, 10}
{4, 5}
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
{1, 2, 3, 6}
{7, 8, 9, 10}
{1, 2, 3, 6, 7, 8, 9, 10}
1.4.4 Dicionários
Os dicionários dictionary()
são objetos mutáveis e iteráveis. Os seus elementos vem sempre aos pares, sendo o primeiro valor uma chave e o segundo elemento, o valor da chave. Tanto a chave e seu valor são objetos Python. A chave é imutável, mas seus valores associados são objetos mutáveis ou não.
= {1: [1,2,3.4,5], 2: 3.7, 3: {1,2,3}}
D 1]
D[2]
D[type(D[3])
type(D[1])
[1, 2, 3.4, 5]
3.7
set
list
Temos um dicionário, com as chaves 1
, 2
e 3
. Para a chave 1
temos uma lista como seu valor, para a chave 2
, temos um valor float
e para a chave 3
, criamos um objeto do tipo conjunto. A seguir, acrescentamos uma chave, nomeada 4
com um valor booleano associado. O método keys()
recupera as chaves do objeto dicionário D
, transforma numa lista e imprime a lista e seu primeiro elemento C[0]
.
4] = True
D[print(D)
= list(D.keys())
C print(C)
0] C[
{1: [1, 2, 3.4, 5], 2: 3.7, 3: {1, 2, 3}, 4: True}
[1, 2, 3, 4]
1
Do mesmo modo,podemos obter os valores das chaves facilmente, como ilustrado a seguir. Posteriormente, veremos como poderemos utilizar uma estrutura de repetição for
para percorrer os elementos do dicionário e armazenar os valores em uma lista ou processá-los um a um.
D.values()list(D.values())
0]] # acessando o valor com chave 1, C[0]
D[C[0] in D # verificando se chave 1 pertence a D
C[5 in D # verificar se a chave 5 pertence a D
dict_values([[1, 2, 3.4, 5], 3.7, {1, 2, 3}, True])
[[1, 2, 3.4, 5], 3.7, {1, 2, 3}, True]
[1, 2, 3.4, 5]
True
False
Podemos deletar o conteúdo de uma chave, usando a função del
ou usando o método pop()
. E podemos atualizar o dicionário, criando novas chaves e valores, com o método update
.
del D[3] # elimina a chave 3
D4) # elimina a chave 4
D.pop(
D5: 'sou novo', 6:'eu também'})
D.update({ D
{1: [1, 2, 3.4, 5], 2: 3.7, 4: True}
True
{1: [1, 2, 3.4, 5], 2: 3.7}
{1: [1, 2, 3.4, 5], 2: 3.7, 5: 'sou novo', 6: 'eu também'}
Podemos criar um objeto dicionário, criando duas tuplas, digamos c
e v
, com as chaves e com os valores das chaves (de mesmo tamanho). Em seguida emparelhamos os elementos com o comando zip(c, v)
. Finalmente, usamos a função dict
para criar o dicionário dos valores emparelhados.
= (1,2,3,4) # chaves
c = ([1,2],True,4.5,('r','s'))
v = dict(zip(c,v))
y
y5,'Chave não existe')
y.get(# get() não gera erro em chave inexistente
# mas, y[5] geraria erro
1)
y.get(1]# como 1 existe, é equivalente ao get() y[
{1: [1, 2], 2: True, 3: 4.5, 4: ('r', 's')}
'Chave não existe'
[1, 2]
[1, 2]
Assim, tendo acesso a um valor da lista, podemos usar os métodos e funções apropriadas para lidarmos com eles. Mais detalhes destes objetos, aparecerão oportunamente, quando avançarmos em mais características da programação em Python.
1.5 Matrizes e Arranjos
As matrizes em Python, como dissemos e mostramos anteriormente, podem ser criadas pelas listas. Assim, vamos criar a seguir uma matriz \(2 \times 2\) usando list para ilustrarmos o procedimento de criação. Se a dimensão for uma só, as listas são arranjos de uma dimensão, conhecidas por vetores.
= [[4,1],[1,1]] # matriz 2 x 2
A print('A = ',A)
1][1] # retorna o valor A[2,2]
A[1][1] = 2 # altera o seu valor
A[ A
A = [[4, 1], [1, 1]]
1
[[4, 1], [1, 2]]
Para lidarmos com funções vetoriais (arrays
) ou matriciais, podemos, entre outras possibilidades usar a biblioteca (pacote) numpy
. Nosso primeiro passo é importar o pacote numpy
com o apelido, np
, que é o mais usado, para facilitar a chamada de seus métodos e funções. Isso só pode ser feito, se já tivermos instalado o pacote numpy
.
del numpy # eliminar a última importação
import numpy as np
Em seguida, criamos uma matriz com o uso da função array()
. Vamos usar nossa lista A
anterior, para fazer isso.
= np.array(A)
B B
array([[4, 1],
[1, 2]])
Podemos criar também a partir de tuplas, em vez de listas, a matriz numpy
. Além disso, existem funções próprias do pacote para criarmos matrizes, como, por exemplo, a matriz de zeros \(2 \times 4\) a seguir. Também existem funções para criarmos arrays
(vetores) unidimensionais, como o método arange()
e o linspace()
. O primeiro cria um vetor indo de n
até o máximo m
(inteiros) sem incluí-lo de \(1\) em \(1\), ou do mínimo n
até o máximo m
(excluindo o máximo) de \(r\) em \(r\): arange(n,m)
ou arange(n,m,r)
. Já o linspace(n,m,s)
inicia em n
, finaliza em m
, mas com passo igual a (m - n) / (s - 1)
.
= np.zeros((2,4))
C print(C)
2,7) # vetor com elementos 2,3,..,6
np.arange(2,7,0.5) # de 2 até 7, de 0.5 em 0,5 (exceto o 7)
np.arange(2,6,6) np.linspace(
[[0. 0. 0. 0.]
[0. 0. 0. 0.]]
array([2, 3, 4, 5, 6])
array([2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. , 6.5])
array([2. , 2.8, 3.6, 4.4, 5.2, 6. ])
Vamos ilustrar alguns cálculos simples com vetores.
= np.arange(2,3,0.2)
x = np.arange(3,6,0.7)
y print('', x,'\n', y)
+ y # adição dos vetores
x * y # produto elementwise
x ** x # potenciação elementwise y
[2. 2.2 2.4 2.6 2.8]
[3. 3.7 4.4 5.1 5.8]
array([5. , 5.9, 6.8, 7.7, 8.6])
array([ 6. , 8.14, 10.56, 13.26, 16.24])
array([ 9. , 17.78458735, 35.01760371, 69.13253113,
137.27719037])
A multiplicação de matrizes por sua vez pode ser feita com a função np.dot
, que tem uma versão na forma de método também, não modificando o objeto que o acionou.
= np.array([[2,1],[1,2]])
C
B
C
np.dot(B,C)
B.dot(C) B
array([[4, 1],
[1, 2]])
array([[2, 1],
[1, 2]])
array([[9, 6],
[4, 5]])
array([[9, 6],
[4, 5]])
array([[4, 1],
[1, 2]])
As inversas podem ser obtidas com a função np.linalg.inv()
e as inversas generalizadas de Moore-Penrose pelo método numpy.linalg.pinv()
. O determinante pode ser obtido por np.linalg.det()
e os autovalores e autovetores (decomposição espectral) por np.linalg.eig()
. É importante observar que os autovalores do np.linalg.eig()
não necessariamente estará ordenado do maior para o menor, como é convencionalmente adotado em diferentes outros programas. Este método é bem geral e pode ser usado em matrizes reais ou complexas quadradas. Alternativamente, para matrizes simétricas o numpy
possui o método np.linalg.eigh()
, que retorna os autovalores em ordem crescente. Veja o exemplo a seguir.
np.linalg.inv(B)# igual a inversa (posto completo)
np.linalg.pinv(B)
np.linalg.det(B)= np.linalg.eig(B)
L, P print('Autovalores: ',L,'\nAutovetores: ',P)
= np.linalg.eigh(B)
L1, P1 print('Autovalores: ',L1,'\nAutovetores: ',P1)
array([[ 0.28571429, -0.14285714],
[-0.14285714, 0.57142857]])
array([[ 0.28571429, -0.14285714],
[-0.14285714, 0.57142857]])
6.999999999999999
Autovalores: [4.41421356 1.58578644]
Autovetores: [[ 0.92387953 -0.38268343]
[ 0.38268343 0.92387953]]
Autovalores: [1.58578644 4.41421356]
Autovetores: [[ 0.38268343 -0.92387953]
[-0.92387953 -0.38268343]]
Também podemos usar a biblioteca scipy
com os métodos sp.linalg.eig()
e sp.linalg.eigh()
, que fazem exatamente como os seus similares da biblioteca numpy
. Para isto devemos importar, depois de instalada, a biblioteca usando import scipy as sp
.
Para matrizes n x m
, podemos usar uma decomposição matricial muito útil para a estatística, em métodos como componentes principais, AMMI, biplot, entre outros. Esse método é chamado de decomposição do valor singular. Nele obtemos a decomposição de uma matriz \(\mathbf{A}\) (m x n)
da seguinte forma: \(\mathbf{A}\) \(=\) \(\mathbf{U} \mathbf{\Lambda} \mathbf{V}^\top\), em que o método np.linalg.svd()
retorna \(\mathbf{U}\) uma matriz \(m \times k\) ortonormal por colunas, \(\mathbf{V}\) uma matriz \(n \times k\) ortonormal por coluna e o vetor correspondente à diagonal de \(\mathbf{\Lambda}\), que é uma matriz diagonal \(k \times k\) de elementos reais positivos, se usarmos a opção full_matrices=False
. As matrizes \(\mathbf{U}\) e \(\mathbf{V}\) são as matrizes dos vetores singulares e a matriz \(\mathbf{\Lambda}\) é a matriz dos valores singulares, sendo \(k\) o posto de \(\mathbf{A}\) que é \(k=\min(n,m)\). O script
a seguir ilustra a obtenção da decomposição singular de uma matriz \(3 \times 2\).
= np.array([[1,4],[2,7],[9, 3]])
A = np.linalg.svd(A, full_matrices=False)
U,L,Vt print('Vetores singulares à esquerda: ',U,
'\nVetor dos valores singulares: ',L,
'\nVetores singulares à direita (transposto): ',Vt)
len(L) # posto de A
# constrói a matriz Lambda np.diag(L)
Vetores singulares à esquerda: [[-0.30248631 -0.39963983]
[-0.54614812 -0.67137377]
[-0.78116852 0.62413562]]
Vetor dos valores singulares: [11.19813546 5.88232625]
Vetores singulares à direita (transposto): [[-0.75238412 -0.65872463]
[ 0.65872463 -0.75238412]]
2
array([[11.19813546, 0. ],
[ 0. , 5.88232625]])
Para verificarmos que a decomposição realmente é adequada, temos o seguinte script
:
np.dot(np.dot(U,np.diag(L)),Vt)# alternativo
U.dot(np.diag(L)).dot(Vt) @ np.diag(L) @ Vt # alternativo U
array([[1., 4.],
[2., 7.],
[9., 3.]])
array([[1., 4.],
[2., 7.],
[9., 3.]])
array([[1., 4.],
[2., 7.],
[9., 3.]])
Muitas outras funções existem no pacote numpy
para lidarmos ou operarmos matrizes. Se necessitarmos de alguma outra função para alguma operação matricial, vamos apresentá-la nestas ocasiões.
1.6 Arquivos de Dados
Usaremos pouco esta estrutura de dados neste material, embora vamos apresentar a biblioteca pandas
para lidarmos com os DataFrames
. Iremos apresentar a leitura de um arquivo particular com duas variáveis X1
e X2
, gravado como arquivo texto separado por espaço entre as colunas (variáveis). O caminho onde este arquivo se encontra em meu computador é: g:/Meu Drive/daniel/Cursos/Estatistica computacional/Apostila/
e seu nome é dados.txt
. Devemos inicialmente instalar a biblioteca pandas
com o comando: pip install pandas
. Posteriormente, carregamos o pacote pandas, como apresentado no script
a seguir. Para mudar o diretório, precisamos importar o pacote os
e usar os.getcwd()
para obter o diretório de trabalho atual e para alterá-lo os.chdir('[path]')
.
import pandas as pd
import os
= os.getcwd() # caminho do projeto
apath 'g:/Meu Drive/daniel/Cursos/Estatistica computacional/Apostila/')
os.chdir( os.getcwd()
'g:\\Meu Drive\\daniel\\Cursos\\Estatistica computacional\\Apostila'
Para lermos um arquivo deste diretório em um objeto DataFrame
podemos usar a função pd.read_csv('dados.txt',r'\s+')
, cujo símbolo r'\s+'
significa que o arquivo é separado por espaços, podendo variar o número de espaços entre colunas de registro (linha) para registro, ou seja, se o número de espaços não está padronizado entre as colunas para as diferentes linhas do arquivo de dados.
= pd.read_csv('dados.txt',sep=r'\s+')
dados dados
X1 | X2 | |
---|---|---|
0 | 13.4 | 14 |
1 | 14.6 | 15 |
2 | 13.5 | 19 |
3 | 15.0 | 23 |
4 | 14.6 | 17 |
5 | 14.0 | 20 |
6 | 16.4 | 21 |
7 | 14.8 | 16 |
8 | 15.2 | 27 |
9 | 15.5 | 34 |
10 | 15.2 | 26 |
11 | 16.9 | 28 |
12 | 14.8 | 24 |
13 | 16.2 | 26 |
14 | 14.7 | 23 |
15 | 14.7 | 9 |
16 | 16.5 | 18 |
17 | 15.4 | 28 |
18 | 15.1 | 17 |
19 | 14.2 | 14 |
Os DataFrames
são estruturas de dados tabular, sendo que cada coluna possui constitui de uma sequência de valores do mesmo tipo (booleano, float, strings, etc.), em que as diferentes colunas podem ser e potencialmente são de diferentes tipos. Os DataFrames
possuem uma coluna adicional chamada index
que no exemplo anterior, do objeto dados
, variou de 0
a 19
, pois nosso DataFrame
possui 20
linhas e duas variáveis X1
e X2
, que no arquivo dados.txt
estavam identificadas na primeira linha física do arquivo que foi lido pelo método read_csv()
. O index
mapeia as linhas do DataFrame
com os labels
mencionados.
Vamos mostrar como construir um DataFrame
diretamente a partir de um objeto dict
para o construtor DataFrame()
do pandas
. Vamos criar um DataFrame
de um delineamento inteiramente casualizado, com 2
tratamentos e 3
repetições de cada um, com os respectivas produtividades avaliadas nas 6
parcelas experimentais.
= {'rep': [1,2,3,1,2,3],
dic 'trat': [1,1,1,2,2,2],
'prod': [3.4,2.3,5.6,5.7,6.3,7.1]}
= pd.DataFrame(dic)
arqd arqd
rep | trat | prod | |
---|---|---|---|
0 | 1 | 1 | 3.4 |
1 | 2 | 1 | 2.3 |
2 | 3 | 1 | 5.6 |
3 | 1 | 2 | 5.7 |
4 | 2 | 2 | 6.3 |
5 | 3 | 2 | 7.1 |
Podemos acrescentar uma nova coluna em nosso DataFrame
, ou eliminarmos uma já existente, criado um novo DataFrame
para receber o resultado, como mostrado a seguir, com a opção columns
, para qualquer ordem das chaves (nomes das colunas).
arqd= pd.DataFrame(arqd,columns=['trat','prod'])
arqd1 # selecionando 2 variáveis no DataFrame
arqd1 'alt'] = [1.5,1.3,1.4,1.2,1.1,1.6] #criando variável altura
arqd[ arqd
rep | trat | prod | |
---|---|---|---|
0 | 1 | 1 | 3.4 |
1 | 2 | 1 | 2.3 |
2 | 3 | 1 | 5.6 |
3 | 1 | 2 | 5.7 |
4 | 2 | 2 | 6.3 |
5 | 3 | 2 | 7.1 |
trat | prod | |
---|---|---|
0 | 1 | 3.4 |
1 | 1 | 2.3 |
2 | 1 | 5.6 |
3 | 2 | 5.7 |
4 | 2 | 6.3 |
5 | 2 | 7.1 |
rep | trat | prod | alt | |
---|---|---|---|---|
0 | 1 | 1 | 3.4 | 1.5 |
1 | 2 | 1 | 2.3 | 1.3 |
2 | 3 | 1 | 5.6 | 1.4 |
3 | 1 | 2 | 5.7 | 1.2 |
4 | 2 | 2 | 6.3 | 1.1 |
5 | 3 | 2 | 7.1 | 1.6 |
Para acessarmos as chaves (colunas), os índices e os valores do DataFrame
podemos usar os seguintes códigos ilustrativos. Observamos que a instrução arqd.prod
para acessar a coluna prod
, resulta em erro, pois prod
é uma palavra reservada (produto). Devemos usar a alternativa anterior para esta chave.
arqd.columns
arqd.index
arqd.values'prod'][0] # produção do índice 0
arqd['prod'] # toda a coluna de produção
arqd[# cuidado, pois prod é palavra reservada: erro
arqd.prod arqd.rep
Index(['rep', 'trat', 'prod', 'alt'], dtype='object')
RangeIndex(start=0, stop=6, step=1)
array([[1. , 1. , 3.4, 1.5],
[2. , 1. , 2.3, 1.3],
[3. , 1. , 5.6, 1.4],
[1. , 2. , 5.7, 1.2],
[2. , 2. , 6.3, 1.1],
[3. , 2. , 7.1, 1.6]])
3.4
0 3.4
1 2.3
2 5.6
3 5.7
4 6.3
5 7.1
Name: prod, dtype: float64
<bound method DataFrame.prod of rep trat prod alt
0 1 1 3.4 1.5
1 2 1 2.3 1.3
2 3 1 5.6 1.4
3 1 2 5.7 1.2
4 2 2 6.3 1.1
5 3 2 7.1 1.6>
0 1
1 2
2 3
3 1
4 2
5 3
Name: rep, dtype: int64
Para extrairmos uma linha inteira usamos o método loc
do DataFrame
associado ao índice da linha (registro), como ilustrado no script
a seguir.
= arqd.loc[1] # segunda linha do DataFrame
L print(L)
= arqd.loc[[0,5]] # as linhas 1 e 6 de arqd
L2 # com os índices 0 e 5 L2
rep 2.0
trat 1.0
prod 2.3
alt 1.3
Name: 1, dtype: float64
rep | trat | prod | alt | |
---|---|---|---|---|
0 | 1 | 1 | 3.4 | 1.5 |
5 | 3 | 2 | 7.1 | 1.6 |
Para selecionar um bloco de registros (linhas) indo de inicio
ao fim
(excluindo o limite final) usamos arqd[inicio:fim]
. Como ilustrado a seguir, onde extraímos do índice 0
até o índice 3
, ou seja, as três primeiras linhas com os índices 0
, 1
e 2
do arqd
.
0:3] arqd[
rep | trat | prod | alt | |
---|---|---|---|---|
0 | 1 | 1 | 3.4 | 1.5 |
1 | 2 | 1 | 2.3 | 1.3 |
2 | 3 | 1 | 5.6 | 1.4 |
Valores perdidos podem fazer parte do DataFrame
e neste caso, eles assumem o valor NaN
, do inglês not a number
. Podemos deletar uma coluna com o comando del, da seguinte forma.
del arqd['alt']
arqd
rep | trat | prod | |
---|---|---|---|
0 | 1 | 1 | 3.4 |
1 | 2 | 1 | 2.3 |
2 | 3 | 1 | 5.6 |
3 | 1 | 2 | 5.7 |
4 | 2 | 2 | 6.3 |
5 | 3 | 2 | 7.1 |
Podemos filtrar impondo condições específicas ao DataFrame
. Por exemplo, se estivermos interessado no DataFrame
resultante dos elementos em que a produção é maior ou igual a 5
, teremos o seguinte resultado. O resultado filtrado mostra os registros nas mesmas posições originais e o seu DataFrame
original arqd
permanece inalterado.
'prod'] >= 5.0]
arqd[arqd[ arqd
rep | trat | prod | |
---|---|---|---|
2 | 3 | 1 | 5.6 |
3 | 1 | 2 | 5.7 |
4 | 2 | 2 | 6.3 |
5 | 3 | 2 | 7.1 |
rep | trat | prod | |
---|---|---|---|
0 | 1 | 1 | 3.4 |
1 | 2 | 1 | 2.3 |
2 | 3 | 1 | 5.6 |
3 | 1 | 2 | 5.7 |
4 | 2 | 2 | 6.3 |
5 | 3 | 2 | 7.1 |
Para gravarmos um DataFrame
podemos escolher o diretório (pasta) e o nome do arquivo e gravarmos em um arquivo csv
(arquivo separado por vírgula) com o comando arqd.to_csv('nome.csv,index=False,header=True)
para não salvar o índice e salvar o cabeçalho. Vamos recuperar em nosso código, o path original com o comando os.chdir(apath)
, em que apath
foi obtido quando iniciamos o assunto sobre DataFrame
e refere-se ao diretório deste projeto. Escolhemos o nome dic.csv
para o arquivo. Podemos ler o arquivo novamente, conforme mostramos nos primeiros passos da abordagem dos DataFrames
. Depois de gravado, repetimos sua leitura e o colocamos no objeto dic
, em que devemos atentar para o separador de colunas, que neste caso é a vírgula.
os.chdir(apath)'dic.csv',index=False,header=True)
arqd.to_csv(= pd.read_csv('dic.csv',sep=r',')
dic dic
rep | trat | prod | |
---|---|---|---|
0 | 1 | 1 | 3.4 |
1 | 2 | 1 | 2.3 |
2 | 3 | 1 | 5.6 |
3 | 1 | 2 | 5.7 |
4 | 2 | 2 | 6.3 |
5 | 3 | 2 | 7.1 |
Em futuras edições, mostraremos mais detalhes dos DataFrames
. Nos capítulos posteriores, caso venhamos a precisar de um DataFrame
e de algumas de suas propriedades, então iremos adicionar os conteúdos necessários nesta ocasião. Falaremos agora das estruturas condicionais e as estruturas de repetições.
1.7 Estruturas de Controle de Programação
O Python é um ambiente de programação em que programas contêm os módulos, os módulos contém instruções, as instruções contém comandos e as expressões criam e processam os objetos. Estas instruções são as atribuições tipo a=b
, a chamada de métodos e funções como print(a)
, if/elif/else
para seleção de ações, o for/else
para realizar iterações, o while/else
para loops
em geral, break
e continue
para controle de loops
, def
para definições de funções, yeld
para gerador de funções, entre outros. As instruções são organizadas em expressões em grupos de comandos, que diferentemente de outras linguagens são organizados pelas indentações (recuo do parágrafo). Assim, o corpo ou grupo de comando de uma instrução específica, ficará reunida se elas tiverem indentadas em relação a instrução principal e com o mesmo nível de indentação. Em outra linguagens os grupos de comandos são reunidos pelas chaves: {grupos de comandos}
. O fim de uma linha termina a instrução, que também pode ser finalizada por um ponto e vírgula. Uma instrução pode continuar em uma nova linha se a linha terminar com o \
ou com o uso dos parênteses.
O nome das variáveis em Python em Python devem iniciar com underscore
ou letra, seguido por números ou letras ou underscore. O python diferencia as maiúsculas das minúsculas, assim Y
é diferente de y
. Os nomes devem evitar as palavras reservadas do Python, como False, None, True, class, and, if, elif, yield, while, break, global, nor, try, return, break,in, etc. Por convenção, as classes em Python começam com maiúsculo e os módulos e nomes de variáveis por minúsculo.
As estruturas condicionais, if/elif/else
são estruturas em Python para selecionar ações. Esta instrução pode conter outras instruções do mesmo tipo ou diferentes instruções em seus grupos de comando. O formato geral é dado por
if condição1:
instruções1elif condição2: # opcional elifs
instruções2else: # opcional else
instruções3
A instrução elif
significa else if
e é opcional. Se a condição1
for verificada, é executado o bloco denominado instruções1
, que estão indentados em relação ao if
. Caso a condição seja falsa, é testada a condição2
e se ela for verdadeira, são executados as instruções denotadas por instruções2
, que pode ser uma simples instrução ou várias instruções, indentadas em relação ao elif
. Finalmente, se a condição2
for falsa são executadas as instruções3
. Cada linha das instruções if
, elif
ou else
são seguidas por um ponto e vírgula. Veja o exemplo simples a seguir.
= 5
x if x > 6:
print('Recebe mais que 6 salários.')
print('Você está entre os 20% mais ricos!')
else:
print('Você recebe 6 salários ou menos.')
print('Você representa 80% da população!')
Você recebe 6 salários ou menos.
Você representa 80% da população!
Para um modelo probabilístico temos o seguinte modelo para a função de distribuição:
\[\begin{align*} F_X(x) =& \left\{ \begin{array}{ll} 0 & \textrm{se } x < 0\\ x^2 & \textrm{se } 0\le x \le 1\\ 1 & \textrm{se } x > 1. \end{array} \right. \end{align*}\]
Para este modelo, temos o seguinte script, no qual decidimos qual parte do programa rodar, conforme os valores de x
são atribuídos.
= 0.8
x if x < 0:
= 0
F elif 0 <= x <= 1:
= x**2
F else:
= 1
F F
0.6400000000000001
As estruturas de repetição do Python são o for
e o while/else
. Existe ainda um terceiro tipo de procedimento em Python para realizarmos iterações. A estrutura geral de um código Python para o for
è apresentado no script
a seguir.
for i in:
instruçoes1else: # opcional instrução else
instruções2
Os objetos do comando for
são os objetos iteradores ou iteráveis, que são aqueles que contém um número contáveis de valores. As listas, tuplas, dicionários e conjuntos ão todos objetos iteráveis. Vamos ilustrar com um simples exemplo a seguir, para calcularmos a soma, o produtório e a média de uma lista de valores.
= [2.3, 4.1, 1.5, 2.3, 4.7]
x = 0
soma = 1
prod = len(x)
n for y in x:
= soma + y
soma = prod * y
prod = soma / n
media print('A soma é: ',soma)
print('O produtório é: ',prod)
print('A média é: ',media)
A soma é: 14.899999999999999
O produtório é: 152.90744999999995
A média é: 2.9799999999999995
Para realizarmos iterações em um dicionário, temos o seguinte exemplo:
= {1: 1.3, 2: 3.1, 3: 1.7}
D for i in D: # iterar nas chaves
print(i, '=', D[i])
print('Agora iterando nas chaves e valores:')
for (i, valor) in D.items():
print(i, '=', valor) # iterar em chave e valor
1 = 1.3
2 = 3.1
3 = 1.7
Agora iterando nas chaves e valores:
1 = 1.3
2 = 3.1
3 = 1.7
Finalmente, um exemplo em um conjunto:
= {1,2,3,4,5}
A for i in A:
print('elemento: ',i)
elemento: 1
elemento: 2
elemento: 3
elemento: 4
elemento: 5
Podemos criar um sequência de valores com o comando range(n)
que vai de 0
a 6
. Assim, também podemos usar o for
nessa sequência, como ilustrado no exemplo a seguir.
= [2.3, 4.1, 1.5, 2.3, 4.7]
x = 0
soma = len(x)
n for i in range(n):
= soma + x[i]
soma = soma / n
media print('A soma é: ',soma)
print('A média é: ',media)
A soma é: 14.899999999999999
A média é: 2.9799999999999995
O while
é uma outra estrutura de repetição, em que o bloco de comandos indentados irão ser executados até que uma condição seja satisfeita. A estrutura geral é dada a seguir.
while condição:
instruçoes1else: # opcional instrução else
instruções2
O exemplo a seguir, ilustra o cálculo do total e da média de uma lista.
= [2.3, 4.1, 1.5, 2.3, 4.7]
x = 0
soma = len(x)
n = 0
i while i < n:
= soma + x[i]
soma = i + 1
i = soma / n
media print('A soma é: ',soma)
print('A média é: ',media)
A soma é: 14.899999999999999
A média é: 2.9799999999999995
Podemos usar os comandos break
e continue
dentro do while (ou do for). O break
é usado após uma segunda condição ser verificada no bloco de comandos do interior da estrutura de repetição e pula a execução do programa para a primeira linha de instrução após o bloco do loop
, ou seja, encerra o loop
. O continue
executa a primeira linha testando a condição primária do while
ou tomando o próximo valor do iterador no for
, ou seja, vai para o início do loop
. Veja o exemplo a seguir.
= 35 # experimente outro número inteiro > 1
y = y // 2
x while x > 1:
if y % x == 0:
print(y, 'tem fator ', x)
break
= x - 1
x else:
print(y,' é primo')
35 tem fator 7
Podemos utilizar a função filter
, como já ilustramos anteriormente neste material, para realizarmos iterações em nosso código. Não daremos mais detalhes disso, por enquanto.
1.8 Funções
As funções em todas as linguagens é uma poderosa ferramenta de programação, que nos permite quebrar um grande problema em pequenas tarefas (as funções), facilitando assim a resolução do problema como um todo. Dizemos que é a estratégia de dividir para conquistar. As funções em geral recebem um objeto e o processa de acordo com as regras definidas em seu bloco de comando. Desta forma a linguagem ganha grande poder, conveniência e elegância. O aprendizado em escrever funções úteis é uma das muitas maneiras de fazer com que o uso do Python seja confortável e produtivo. A sintaxe geral de uma função é dada por:
def nome(arg1, arg2,...,argn):
instruções
As instruções significam um bloco de comandos (indentados) e podem ou não ter o comando de return objeto
, que pode acontecer em qualquer parte do bloco de comandos. A função pode não ter este comando de return
, se ela modificar um arquivo apenas gravando um novo resultado ou se imprimir uma mensagem quando chamada. Os argumentos ou parâmetros são passado para a função e a sua chamada deve obedecer estritamente a ordem em que eles aparecem, a menos que a chamada seja com chave, ou seja, do tipo `arg1 = 2.3
, por exemplo. Neste caso, os argumentos podem ser colocados em qualquer ordem. Os argumentos de uma função podem conter valores default
, ou seja, na declaração do nome da função podemos ter algo do tipo: def nome(x, theta = 0.5)
. O argumento theta=0.5
pode ser omitido na chamada da função, que será atribuído seu valor 0,5
padrão.
Vamos apresentar uma função simples para testar a hipótese \(H_0: \mu=\mu_0\) a partir de uma amostra simples de uma distribuição normal. Dois argumentos serão utilizados: o vetor (lista) de dados \(x\) de tamanho \(n\) e o valor real hipotético \(\mu_0\). A função calculará o valor da estatística \(t_c\) do teste por:
\[\begin{align}\label{ecp01:eq:tstudent} t_c = & \dfrac{\bar{X}-\mu_0}{\frac{S}{\sqrt{n}}}. \end{align}\]
A função resultante, em Python, é apresentada a seguir. Neste exemplo uma amostra de tamanho \(n = 8\) foi utilizada para obter o valor da estatística para testar a hipótese \(H_0: \mu=3,0\) (se a amostra era proveniente do povo na’vu). Podemos observar que o resultado final da função é igual ao do último comando executado, ou seja o valor da estatística e do valor-\(p\), por meio de um objeto do tipo dicionário. Esta função utiliza no seu escopo três funções do Python (funções básicas do numpy
), ainda não apresentadas. As duas primeiras, var()
e mean()
retornam a variância e a média do vetor utilizado como argumento, respectivamente, e a terceira, pt()
, retorna a probabilidade acumulada da distribuição \(t\) de Student para o primeiro argumento da função com \(\nu\) graus de liberdade, que é o seu segundo argumento.
import scipy as sp # para calcular probabilidade da t
def t_test(x, mu0):
= len(x)
n = np.var(x,ddof=1) # ddof=1, divisor n-1 para s2
s2 = np.mean(x)
xb = {'tc':0,'p.val':0}
t 'tc'] = (xb-mu0) / (s2 / n)**0.5
t['p.val'] = 2*(1-sp.stats.t.cdf(abs(t['tc']),n-1))
t[return t
= [1.76,1.81,1.74,1.71,1.79,1.75]
y = t_test(y, 3.0) # altura de avatares
t print('tc = ',t['tc'])
print('p.val = ',t['p.val'])
tc = -84.89699641330068
p.val = 4.297311617662558e-09
Podemos reescreve esta função para colocarmos um valor default
para o argumento mu0
. Se escolhêssemos o valor 0
e em alguma chamada da função, esse argumento fosse omitido, seria feito o teste da hipótese \(H_0: \mu=0\) por padrão.
def t_test(x, mu0 = 0):
= len(x)
n = np.var(x,ddof=1) # ddof=1, divisor n-1 para s2
s2 = np.mean(x)
xb = {'tc':0,'p.val':0,'S2': s2, 'xbar': xb}
t 'tc'] = (xb-mu0) / (s2 / n)**0.5
t['p.val'] = 2*(1-sp.stats.t.cdf(abs(t['tc']),n-1))
t[return t
= [1.76,1.81,1.74,1.71,1.79,1.75]
y = t_test(y) # teste H0: mu0=0
t print('tc = ',t['tc'])
print('p.val = ',t['p.val'])
print(list(t.items())[2:4])
tc = 120.49896265113645
p.val = 7.465636997494585e-10
[('S2', 0.001280000000000002), ('xbar', 1.76)]
Vamos realizar um test para a correlação em populações normais bivariadas. Assim, dado o par de variáveis vetoriais x
e y
tomados em n
indivíduos, temos a hipótese nula
\[\begin{align*} H_0:&\, \rho=0 \end{align*}\]
e a estatística do teste sob \(H_0\) dada por
\[\begin{align*} t_c =& \dfrac{r\sqrt{n-2}}{\sqrt{1-r^2}}, \end{align*}\]
em que \(r\) é o coeficiente de correlação amostral entre \(X\) e \(Y\); e \(n\) é o tamanho da amostra. Sob \(H_0\), essa estatística segue a distribuição \(t\) de Student com \(\nu=n-2\) graus de liberdade.
A função deve receber os vetores \(\mathbf{x}\) e \(\mathbf{y}\) e retornar o resultado do teste: estatística e valor-\(p\). Pode-se utilizar a função cor
do Python scipy
para obter a correlação entre \(\mathbf{x}\) e \(\mathbf{y}\).
def cor_test(x, y):
= len(x)
n if n != len(y):
print('Listas devem ter o mesmo tamanho!')
return
= np.corrcoef(x,y)[0,1]
r = {'tc':0,'p.val':0,'r': r}
t 'tc'] = r * (n-2)**0.5 / (1 - r**2)**0.5
t['p.val'] =2*(1-sp.stats.t.cdf(abs(t['tc']),n-2))
t[return t
= [1, 2, 3.1, 4.2]
x = [2.1, 3.9, 6.1, 8.3]
y = cor_test(x, y)
t print('tc = ',t['tc'])
print('p.val = ',t['p.val'])
print('r = ',t['r'])
tc = 58.469836352468235
p.val = 0.0002923787083537466
r = 0.9997076212916461
Finalmente, vamos obter uma função para obtermos potências reais de matrizes quadradas simétricas positivas definidas. Para isso vamos escrever uma função para obter potências reais de uma matriz \(\mathbf{A}\) simétrica e positiva definida:
\[\begin{align*} \mathbf{A}=& \mathbf{P}\mathbf{\Lambda} \mathbf{P}^\top, \end{align*}\]
sendo a potência de ordem \(\alpha\in \mathbb{R}\) dada por
\[\begin{align*} \mathbf{A}^\alpha=& \mathbf{P} \mathbf{\Lambda}^\alpha \mathbf{P}^\top. \end{align*}\]
Deve receber \(\mathbf{A}\) e retornar \(\mathbf{A}^\alpha\). O script
a seguir ilustra uma função para obtermos estas potências matriciais. Observe que não é uma potência elemento a elemento. Também devemos observar que não é importante que os autovalores estejam ordenados. Mas é óbvio que os autovetores associados a cada autovalor deve estar corretamente associado e preservado e isso é feito pela biblioteca numpy
por meio da função eig()
.
def mat_power(A, alpha = 0.5):
= np.linalg.eig(A)
e_val, e_vec if any(e_val) < 0:
print('Matriz não é positiva definida!')
return
= e_vec.dot(np.diag(e_val**alpha)).dot(np.transpose(e_vec))
Ap return Ap
= [[4,1],[1,2]]
A print('A = ',A)
# raiz quadrada
mat_power(A) = mat_power(A, 1/3) # raiz cúbica
A3
A3# verificando A3.dot(A3).dot(A3)
A = [[4, 1], [1, 2]]
array([[1.97773553, 0.29759397],
[0.29759397, 1.38254759]])
array([[1.57094963, 0.16768037],
[0.16768037, 1.23558888]])
array([[4., 1.],
[1., 2.]])
1.9 Estatística Computacional
Os métodos de computação intensiva têm desempenhado um papel cada vez mais importante para resolver problemas de diferentes áreas da ciência. Vamos apresentar algoritmos para gerar realizações de variáveis aleatórias de diversas distribuições de probabilidade, para realizar operações matriciais, para realizar inferências utilizando métodos de permutação e bootstrap, etc. Assim, buscamos realizar uma divisão deste material em uma seção básica e em outra aplicada. As técnicas computacionais são denominadas de estatística computacional se forem usadas para realizarmos inferências, para gerarmos realizações de variáveis aleatórias ou para compararmos métodos e técnicas estatísticas.
Vamos explorar métodos de geração de realizações de variáveis aleatórias de diversos modelos probabilísticos, para manipularmos matrizes, para obtermos quadraturas de funções de distribuição de diversos modelos probabilísticos e de funções especiais na estatística e finalmente vamos apresentar os métodos de computação intensiva para realizarmos inferências em diferentes situações reais. Temos a intenção de criar algoritmos em linguagem Python e posteriormente, quando existirem, apresentar os comandos para acessarmos os mesmos algoritmos já implementados.
Vamos apresentar os métodos de bootstrap e Monte Carlo, os testes de permutação e o procedimento jackknife para realizarmos inferências nas mais diferentes situações reais. Assim, este curso tem basicamente duas intenções: possibilitar ao aluno realizar suas próprias simulações e permitir que realizem suas inferências de interesse em situações em que seria altamente complexo o uso da inferência clássica.
Seja na inferência frequentista ou na inferência Bayesiana, os métodos de simulação de números aleatórios de diferentes modelos probabilísticos assumem grande importância. Para utilizarmos de uma forma mais eficiente a estatística computacional, um conhecimento mínimo de simulação de realizações de variáveis aleatórias é uma necessidade que não deve ser ignorada. Vamos dar grande ênfase a este assunto, sem descuidar dos demais. Apresentaremos neste material diversos algoritmos desenvolvidos e adaptados para a linguagem Python.
Simular é a arte de construir modelos segundo Naylor et al. (1971), com o objetivo de imitar o funcionamento de um sistema real, para averiguarmos o que aconteceria se fossem feitas alterações no seu funcionamento (Dachs (1988)). Este tipo de procedimento pode ter um custo baixo, evitar prejuízos por não utilizarmos procedimentos inadequados e otimizar a decisão e o funcionamento do sistema real.
Precauções contra erros devem ser tomadas quando realizamos algum tipo de simulação. Podemos enumerar:
escolha inadequada das distribuições;
simplificação inadequada da realidade; e
erros de implementação.
Devemos fazer o sistema simulado operar nas condições do sistema real e verificar por meio de alguns testes se os resultados estão de acordo com o que se observa no sistema real. A este processo denominamos de validação. A simulação é uma técnica que usamos para a solução de problemas. Se a solução alcançada for mais rápida, com eficiência igual ou superior, de menor custo e de fácil interpretação em relação a outro método qualquer, o uso de simulação é justificável.
1.10 Exercícios
Criar no Python os vetores \(\mathbf{a}^\top=[4, 2, 1, 5]\) e \(\mathbf{b}^\top=[6, 3, 8, 9]\) e concatená-los formando um único vetor. Obter o vetor \(\mathbf{c}=2\mathbf{a} - \mathbf{b}\) e o vetor \(\mathbf{d}\) \(=\) \(\mathbf{b}^\top \mathbf{a}\). Criar uma sequência cujo valor inicial é igual a \(2\) e o valor final é \(30\) e cujo passo é igual a \(2\). Replicar cada valor da sequência \(4\) vezes de duas formas diferentes (valores replicados ficam agregados e a sequência toda se replica sem que os valores iguais fiquem agregados).
Selecionar o subvetor de \(\mathbf{x}^\top=[4, 3, 5, 7, 9, 10]\) cujos elementos são menores ou iguais a \(7\).
Criar a matriz
\[\begin{align*} \mathbf{A}= \left[\begin{array}{cc} 10 & 1 \\ 1 & 2 \end{array} \right] \end{align*}\]
e determinar os autovalores e a decomposição espectral de \(\mathbf{A}\).
Construir uma função para verificar quantos elementos de um vetor de dimensão \(n\) são menores ou iguais a uma constante \(k\), real. Utilize as estruturas de repetições
for
ewhile
para realizar tal tarefa (cada uma destas estruturas deverá ser implementada em uma diferente função). Existe algum procedimento mais eficiente para gerarmos tal função sem utilizar estruturas de repetições? Se sim, implementá-lo.Implementar uma função R para realizar o teste \(t\) de Student para duas amostras independentes. Considerar os casos de variâncias heterogêneas e homogêneas. Utilizar uma estrutura condicional para aplicar o teste apropriado, caso as variâncias sejam heterogêneas ou homogêneas. A decisão deve ser baseada em um teste de homogeneidade de variâncias. Para realizar tal tarefa implementar uma função específica assumindo normalidade das amostras aleatórias.
Criar uma função para obter a inversa de Moore-Penrose de uma matriz qualquer \(n\) \(\times\) \(m\), baseado na decomposição do valor singular, função
svd
donp.linalg.svd
. Seja para isso uma matriz \(\mathbf{A}\), cuja decomposição do valor singular é \(\mathbf{A}\) \(=\) \(\mathbf{U}\mathbf{D}\mathbf{V}^\top\), em que \(\mathbf{D}\) é a matriz diagonal dos valores singulares e \(\mathbf{U}\) e \(\mathbf{V}\) são os vetores singulares correspondentes. A inversa de Moore-Penrose de \(\mathbf{A}\) é definida por \(\mathbf{A}^+\) \(=\) \(\mathbf{V}\mathbf{D}^{-1}\mathbf{U}^\top\).