Python: Sequências e Coleções


Introdução

Sequências: no Python sequências são conjuntos de elementos ordenados que podem ser acessados em sua ordem (o que chamamos de iteráveis. Já vimos que strings são sequências. Por ex., print('Casa da Mãe Joana'[3]) resulta em 'a'.

range(), na verdade não é uma função mas uma classe geradora de um iterador, como veremos mais tarde. Ela fornece objetos que produzem os elementos sob demanda, sem precisar guardar todos na memória.

Outra sequência de uso comum é range, que é o tipo de objeto retornado pela “função” range(). Ela tem a seguinte sintaxe ou assinatura:

range(inicio, fim, passo)

onde inicio e passo são opcionais. Ela gera uma sequência de inicio até fim (exclusivo) com intervalo de passo.

» faixa = range(2, 20, 3)
» print('o objeto é', faixa)
↳ o objeto é range(2, 20, 3)
» print('o tipo do objeto é', type(faixa))
↳ o tipo do objeto é <class 'range'>
» print('o segundo elemento é', faixa[1])
↳ o segundo elemento é 5

# podemos iterar pelos itens de faixa
» for t in faixa:
»     print(t, end=' ')
↳ 2 5 8 11 14 17 
Conteineres” são estruturas de dados que organizam e agrupam dados de acordo com seu tipo.

Coleções são conteineres de dados. No módulo básico do Python existem quatro tipos de dados em coleções:

  • Lista (list): ordenada e mutável, pode ter membros duplicados.
  • Tupla (tuple): ordenada e imutável, pode ter membros duplicados.
  • Conjunto (set): não ordenada e não indexada, sem membros duplicados.
  • Dicionário (dictionary): não ordenada, mutável, sem membros duplicados.

Observe que sets são mutáveis mas não admitem objetos mutáveis como seus elementos. Podemos ter, portanto, conjuntos de inteiros, strings ou tuplas, mas não de listas ou dicionários.

Existe um módulo chamado collections com outros tipos de conteineres com estruturas de dados mais específicas como, por exemplo, uma tupla nomeada e um dicionário ordenado.

Listas

Além dos objetos já vistos, muitos outros são pre-programados no núcleo básico do Python. A listas (lists) são sequências ordenadas de objetos que podem ser acessados por meio de seu índice (ou index), um marcador de sua posição. Os objetos de uma lista não precisam ser do mesmo tipo, são delimitados por colchetes e separados por vírgulas.

lista = [valor_0, …, valor_n]

As listas são mutáveis, podem ser encolhidas ou expandidas e ter seus elementos substituídos. Exemplos de listas são dados a seguir:

» lista1 = ['Maria', 25]
» lista2 = ['José', 27]
» lista3 = [lista1, lista2]
» print(lista1[0], lista1[1])
↳ Maria 25

# lista3 é uma lista de listas
» print(lista3)
↳ [['Maria', 25], ['José', 27]]

# o 2º elemento da 2ª lista é
» print(lista3[1][1])
↳ 27

Da mesma forma que sequências de caracteres (nas strings), elementos em listas e tuplas podem ser acessados por meio de seus índices. Fatias (ou slices) lista[i:j] se iniciam no i-ésimo elemento, até o j-ésimo, exclusive, de forma que len(lista[i:j]) = j-i. Índices negativos contam a partir do final.

» lista4 = [10, 23, 45, 56, 67, 78, 89, 90]
» lista4[1:3]
↳ [23, 45]

# omitindo o 1º índice
» lista4[:2]
↳ [10, 23]

# omitindo o 2º índice
» lista4[4:]
↳ [67, 78, 89, 90]

# o último elemento
» lista4[-1]
↳ 90

» lista4[-4:-2]
↳ [67, 78]

# len fornece o número de elementos na lista
» len(lista4[3:7])
↳ 4

# um 3º parâmetro indica o "passo"
» lista4[::2]
↳ [10, 45, 67, 89]

# um passo negativo indica contagem do fim para o início
» lista4[::-1]
↳ [90, 89, 78, 67, 56, 45, 23, 10]


Vimos que a ausência do 1º índice assume o início, a ausência do 2º assume o final. O 3º indica para pular um número de elementos.

Uma função que retorna uma lista (ou outra sequência qualquer) pode ser diretamente indexada. Por ex., podemos construir uma função que retorna o mês abreviado em 3 letras. O índice do mês pode ser atribuído diretamente ao retorno da função:

» def mes_abrev():
»     m = ['jan','fev','mar','abr','mai','jun','jul','ago','set','out','nov','dez',]
»     return m

# para pegar o segundo mês:
» mes_abrev()[1]
↳ 'fev'

# para pegar o 1º trimestre:
» mes_abrev()[:3]
↳ ['jan', 'fev', 'mar']

# para pegar o último trimestre:
» mes_abrev()[-3:]
↳ ['out', 'nov', 'dez']

Assim como ocorre com strings, listas podem ser somadas (concatenadas) e multiplicadas por um número (repetidas). O efeito é o mesmo.

» lista5 = [0, 1, 2, 3, 4]
» lista6 = [10, 11, 12, 13, 14]
» lista5 + lista6
↳ [0, 1, 2, 3, 4, 10, 11, 12, 13, 14]

» lista5 * 2
↳ [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

Uma lista pode ser inicializada vazia ou com um número especificado de itens.

» lista_vazia = []      # lista vazia    
» lista_vazia
↳ []

# elementos podem ser inseridos com o método append
» lista_vazia.append(12)
» lista_vazia.append(13)
» lista_vazia
↳ [12, 13]

# o mesmo efeito seria obtido com concatenação
» lista_vazia.clear()     # a lista volta a ser vazia
» lista_vazia += [12, 13]
» lista_vazia
↳ [12, 13]

# lista com 5 entradas
» lista_none = [None]*5
» lista_none
↳ [None, None, None, None, None]

Sendo mutáveis listas podem ser alteradas in place tendo qualquer de seus valores trocados sem a necessidade de criação de nova lista. count(item) retorna quantas vezes item aparece na lista. index(item) retorna o índice onde item aparece.

» palavras = ['palha', 'grande', 'casa', 'dado', 'pequeno',
              'coisa', 'gado', 'fato', 'gato', 'lá' ]
» print('A terceira palavra é ---| %s |---' % palavras[2] )
↳ A terceira palavra é ---| casa |---

» palavras[2] = 'house'
» print('A terceira palavra é ---| %s |---' % palavras[2] )
↳ A terceira palavra é ---| house |---

# inserindo mais uma 'house'
» palavras[3] = 'house'
» palavras.count('house')
↳ 2

# o índice de 'coisa'
» palavras.index('coisa')
↳ 5

# index retorna a 1ª ocorrência, quando existem mais de 1
» palavras.index('house')
↳ 2

Objetos podem ser inseridos em uma lista, na n-ésima posição, com lista.insert(n, obj). Um membro do lista pode ser removido com lista.pop(n). Esse método retorna o objeto removido. lista.remove(obj) também faz a remoção, sem retornar o objeto.

# inserir um objeto na posição 3
» palavras.insert(3, 'estrela')
» print(palavras)
↳ ['pequeno', 'palha', 'lá', 'estrela', 'house', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']

# pop extrai e retira elemento no índice dado
» saiu = palavras.pop(3)
» print(saiu)
↳ estrela

# a lista fica sem esse elemento
» print(palavras)
↳ ['pequeno', 'palha', 'lá', 'house', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']

# se nenhum índice for fornecido o último elemento é removido (e retornado)
» palavras.pop()
↳ coisa
» print(palavras)
↳ ['pequeno', 'palha', 'lá', 'house', 'house', 'grande', 'gato', 'gado', 'fato']

# remove não retorna o item removido
» palavras.remove('house')
» print(palavras)
↳ ['pequeno', 'palha', 'lá', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']

# o item deve estar na lista ou exceção será lançada
» palavras.remove('pedra')
↳ ValueError: list.remove(x): x not in list

O método .sort() ordena itens de uma lista. Ele admite os parâmetros opcionais key, reverse que podem ser usados para fazer ordenamentos diferentes que o default. Por ex., se key = len o ordenamento se dará por comprimento das palavras, da menor para a maior. Se, além disso reverse = True o ordenamento se dará no sentido contrário. Também se pode definir uma função customizada para fazer essa ordenação.

# ordenar
» palavras.sort()
» print(palavras)
↳ ['coisa', 'fato', 'gado', 'gato', 'grande', 'house', 'house', 'lá', 'palha', 'pequeno']

# outra form de ordenar item em qualquer sequência é a função sorted()
# que retorna a lista palavras ordenada sem alterar a lista original
» sorted(palavras)    # nesse caso a lista já estava ordenada
↳ ['coisa', 'fato', 'gado', 'gato', 'grande', 'house', 'house', 'lá', 'palha', 'pequeno']

# inverter a ordenação
» palavras.reverse()
» print(palavras)
↳ ['pequeno', 'palha', 'lá', 'house', 'house', 'grande', 'gato', 'gado', 'fato', 'coisa']

» palavras.sort(key= len)
» palavras
↳ ['lá', 'fato', 'gado', 'gato', 'coisa', 'house', 'house', 'palha', 'grande', 'pequeno']

» palavras.sort(key= len, reverse=True)
↳ ['pequeno', 'grande', 'coisa', 'house', 'house', 'palha', 'fato', 'gado', 'gato', 'lá']

Métodos das listas (lists)

Método Descrição
append() insere elementos na lista
clear() remove todos os elementos na lista
copy() retorna uma cópia da lista
count() retorna o número de elementos com valor especificado
extend() insere os elementos de outra lista (ou iterável) ao final da lista
index() retorna o índice do 1º elemento com valor especificado
insert() insere elemento em posição especificada
pop() remove elemento em posição especificada por índice e retorna esse elemento
remove() remove elemento em posição especificada por índice
reverse() inverte a ordem da lista
sort() ordena a lista

Além desses as seguintes funções são úteis para se tratar com listas e outras sequências:

Função Descrição
cmp(x, y) compara dois valores
len(seq) retorna o comprimento da sequência
list(seq) converte uma sequência em lista
max(args) retorna o valor máximo na sequência
min(args) retorna o valor mínimo na sequência
reversed(seq) retorna um iterador com os valores na sequência
sorted(seq) retorna lista ordenada dos elementos na sequência
tuple(seq) converte a sequência em uma tupla

É possível testar se um determinado elemento é membro da lista:

» 'gado' in palavras
↳ True

» 'pedra' in palavras
↳ False

# definimos uma lista de listas (com nome e senha de usuários)
» usuarios = [
»   ['alberto', '1234'],
»   ['mario', '6282'],
»   ['maria', '5274'],
»   ['joana', '9943']
» ]
» nome = input('Nome do usuário: ')
» pin = input('Código PIN: ')
» msg = 'Accesso liberado' if [nome, pin] in usuarios else 'Acesso negado'
» print(msg)

# uma entrada de dados que não corresponde a nenhuma entrada da lista
↳ Nome do usuário: lucas
↳ Código PIN: 1234
↳ Acesso negado

# dados de usuário cadastrado
↳ Nome do usuário: joana
↳ Código PIN: 9943
↳ Accesso liberado

Assim como existe uma função interna len() que retorna o comprimento de sequências, temos também max(), min() que retornam o maior e menor valor dentro da lista. Essas funções funcionam também com strings, considerada a ordem alfabética.

» numeros = [100, 23, 987]
» print(len(numeros))
↳ 3

» print(max(numeros))
↳ 987

» print(min(numeros))
↳ 23

» print(max(2, 3))
↳ 3

» print(min(9, 3, 2, 5))
↳ 2

# min e max fazem comparações entre strings:
» palavras
↳ ['pequeno', 'palha', 'lá', 'grande', 'gato', 'gado', 'fato', 'coisa']

» min(palavras)
↳ 'coisa'

» max(palavras)
↳ 'pequeno'

Tuplas

Tuplas (tuples) são sequências ordenadas e imutáveis de objetos, que podem ser acessados por meio de seu índice (index), um marcador de sua posição. Os objetos que a compõem não precisam ser mesmo tipo e são delimitados por parênteses e separados por vírgulas.

lista = (valor_0, …, valor_n)

Tuplas e listas se comportam de modo análogo, exceto em que tuplas são imutáveis (como as strings), não podem ser alteradas após sua criação. Existem razões técnicas para a existência de tuplas: operações com tuplas são mais rápidas que as com listas, e elas ocupam menos espaço na memória. Por isso não é raro que um método retorne uma tupla ou as demande como parâmetro. Daí a necessidade de conhecê-las.

# é comum usar tuplas na formatação de strings:
» print('Essa frase %s %d %s %s %s.' % ('lê', 4, 'palavras','na','tupla'))
↳ Essa frase lê 4 palavras na tupla.

# elementos são acessados por índice
» tupla = (0,1,2,3,4,'cinco')
» tupla[5]
↳ 'cinco'

# tuplas são imutáveis
» tupla[5] = 5
↳ TypeError: 'tuple' object does not support item assignment

# um erro é lançado se o índice não existe no objeto 
» tupla[6]
↳ IndexError: tuple index out of range

# tuplas são iteráveis
» for t in tupla:
»     print(t, end=', ')
↳ 0, 1, 2, 3, 4, cinco,

# a função len funciona para tuplas
» print(tupla[len(tupla) - 1])
↳ cinco

# é o mesmo que
» print(tupla[-1])
↳ cinco

# tuplas podem conter outras tuplas como elemento
» a = ('primeiro', 'segundo', 'terceiro')
» b = (a, 'segundo elemento de b')
» print('%s' % b[1])
↳ segundo elemento de b

» print(b[0][0], b[0][1], b[0][2])
↳ primeiro segundo terceiro

Métodos das Tuplas (tuples)

Método Descrição
count() retorna quantas vezes um valor especificado ocorre na tupla
index() procura por valor especificado e retorna sua posição

Conjuntos (sets)

Conjuntos (sets) são coleções não ordenadas de objetos únicos e imutáveis. Conjuntos podem ser criados listando-se diretamente os elementos ou passando-se uma sequência pelo construtor set().

conjunto = {e_1, …, e_n}

conjunto = set(sequencia)

Por exemplo, abaixo dois sets são criados e as operações de interseção, união e diferença são mostradas. Nenhuma dessa operações alteram os sets envolvidos.

# dois sets são criados
» X = {'a', 'b', 'c', 'd'}
» Y = set('cdef')
» print(X, Y)
↳ {'d', 'c', 'b', 'a'} {'f', 'c', 'd', 'e'}

# a interseção é obtida com & (e comercial)
» X & Y
↳ {'c', 'd'}

# a união com |
» X | Y
↳ {'a', 'b', 'c', 'd', 'e', 'f'}

# a diferença de conjuntos
» X-Y
↳ {'a', 'b'}

# comprehension (veremos mais sobre esses métodos mais tarde)
» Z = {i**2 for i in [1,2,3,4] }
» Z
↳ {1, 4, 9, 16}

» type(Z)
↳ set

# os elementos podem ser percorridos um a um (mas não de forma ordenada)
» for i in Z:
»     print(i, end=', ')
↳ 16, 1, 4, 9, 

# testes para pertinência
» 16 in Z
↳ True
» 5 in Z
↳ False

Alternativamente, as operações de interseção, união e diferença podem feitas com métodos da classe. Nesse caso o conjunto X fica alterado na operação.

» X = {'a', 'b', 'c', 'd'}
» Y = {'c', 'd', 'e', 'f'}

# a união de dois sets (fica armazenada em X)
» X.union(Y)
» print(X)
↳ {'a', 'b', 'c', 'd', 'e', 'f'}

» X = {'a', 'b', 'c', 'd'}
» Y = {'c', 'd', 'e', 'f'}

# elementos que não são comuns
» X.symmetric_difference_update(Y)
» print(X)
↳ {'a', 'b', 'e', 'f'}

» X = {'a', 'b', 'c', 'd'}
» Y = {'c', 'd', 'e', 'f'}
# elementos em ambos os sets (interseção)
» X.intersection_update(Y)
» print(X)
↳ {'c', 'd'}

Outros métodos de sets são mostrados.

# o comprimento é o número de elementos
» Z = {1, 4, 9, 16}
» len(Z)
↳ 4

# novos elementos podem ser adicionados
» Z.add(133)
» Z
↳ {1, 4, 9, 16, 133}

# qualquer sequência pode ser adicionada
» qualquer =[1, 100, 'coisa']
» Z.update(qualquer)
» Z
↳ {1, 100, 133, 16, 4, 9, 'coisa'}

# um elemento pode ser removido
» Z.remove('coisa')
» Z
↳ {1, 4, 9, 16, 100, 133}

# um erro é lançado no uso de remove se elemento não existe
» Z.remove(45)
↳ KeyError: 45

# também se pode remover com discard
» Z.discard(133)
» Z
↳ {1, 4, 9, 16, 100}

# nenhum erro é lançado com discard
» Z.discard(45)

# para limpar todos os elementos do set
» Z.clear()
» Z
↳ set()

# para apagar a variável (válido para qualquer variável do Python)
» del Z
» Z
↳ NameError: name 'Z' is not defined

Suponha que temos uma lista longa de elementos muitos dos quais podem ser repetidos e queremos que essa lista não contenha repetições. Para remover repetições podemos transformar a lista em set (que não contém repetições) e depois retornando os dados para uma lista, caso isso seja necessário.

# queremos remover as repetições de
» lista_original = [1,2,3,4,4,3,2,1,6,6,7,8,8,8,9]
» conjunto = set(lista_original)
» lista_nova= list(conjunto)
» lista_nova
↳ [1, 2, 3, 4, 6, 7, 8, 9]

Seguem mais algumas ilustrações de uso de métodos de sets.

# criamos um set usando compreensão
» nSet = {i**3 for i in range(4)}
» nSet
↳ {0, 1, 8, 27}

# o método pop()  extrai um elemento qualquer e o retorna †
» print(nSet.pop())
↳ 0
» print(nSet)
↳ {1, 27, 8}

» print(nSet.pop())
↳ 1
» print(nSet)
↳ {27, 8}

# subset e superset
» A = {1,2}
» B = {1,2,3}
» A.issubset(B)
↳ True

» A.issuperset(B)
↳ False

» B.issuperset(A)
↳ True


Observe que set.pop() remove e retorna um elemento qualquer do conjunto, uma vez que ele não é ordenado.

Métodos dos conjuntos (set)

Método Descrição
add() insere elemento no set
clear() remove todos os elementos do set
copy() retorna cópia do set
difference() retorna um set com a diferença entre 2 ou mais sets
difference_update() remove elementos incluidos no segundo set
discard() remove item especificado
intersection() retorna o set interseção de 2 sets
intersection_update() remove items do set não presentes no segundo set especificado
isdisjoint() retorna True se os 2 sets são disjuntos
issubset() retorna True se o set é subconjunto do segundo set
issuperset() retorna True se o set contém o segundo set
pop() remove (e retorna) um elemento arbitrário do set
remove() remove o elemento especificado
symmetric_difference() retorna o set com a diferença simétrica de dois sets
symmetric_difference_update() insere a diferença simétrica desse set em outro
union() retorna um set com a união dos sets
update() atualiza o primeiro set com sua união com um ou mais sets

Dicionários (dictionaries)

Dicionários (dictionaries) são coleções de dados armazenados em pares chave: valor (key: value). A coleção é mutável, ordenada (a partir de Python 3.7) e não admite valores duplicados. A chave de um dicionário funciona como um índice que permite a recuperação do valor a ele associado. Eles têm a forma geral de

dict = {key_1:value_1, …, key_n:value_n}

As chaves são ordenadas e podem ser de diversos tipos. Valores podem ser de qualquer tipo e podem ser alterados. Por exemplo:

# inicializando um dicionário
» dic = {'casa':'house', 'cachorro':'dog', 'caneta':'pencil','carro':'car'}
» dic
↳ {'casa': 'house', 'cachorro': 'dog', 'caneta': 'pencil', 'carro': 'car'}

» print(type(dic)) 
↳ <class 'dict'>

# acessando o valor com chave = 'caneta'
» dic['caneta']
↳ 'pencil'

# a função len retorna quantos pares existem no dicionário
» len(dic)
↳ 4

# chaves duplicadas são substituídas (a anterior é removida)
» dic2 = {"nome" : "Pedro",
»         "sobrenome" : "Alvarez",
»         "idade" : 23,
»         "idade" : 27
»         }
» print(dic2['idade'])
↳ 27

Um dicionário pode ser criado recebendo uma lista de tuplas em seu construtor, como se mostra no primeiro exemplo abaixo. No exemplo seguinte tuplas são usadas como chaves.

# t é uma lista de tuplas
» t = [(0, 'zero'),(1, 'um'),(2, 'dois'),(3, 'tres'),(4, 'quatro')]
» d = dict(t)

» print(d)
↳ {0: 'zero', 1: 'um', 2: 'dois', 3: 'tres', 4: 'quatro'}
» d[3]
↳ 'tres'

# usando tuplas como chaves (listas não podem ser usadas)
» tele = dict()
» tele['Newton', 'Isaac'] = 1643
» tele['Curie','Marie'] = 1867
» tele['Einstein','Albert'] = 1879
» tele['Hawking','Stephen'] = 1942

» print(tele)
↳ {('Newton','Isaac'): 1643, ('Curie', 'Marie'): 1867, ('Einstein', 'Albert'): 1879, ('Hawking', 'Stephen'): 1942}
# o índice é uma tupla
» print(tele['Curie', 'Marie']) # ou print(tele[('Curie', 'Marie')])
↳ 1867

# o dicionário pode ser percorrido lendo-se os dois valores da tupla
» for sobrenome, nome in tele:
»     print(nome, sobrenome, "nasceu em", tele[sobrenome,nome])

↳ Isaac Newton nasceu em 1643
↳ Marie Curie nasceu em 1867
↳ Albert Einstein nasceu em 1879
↳ Stephen Hawking nasceu em 1942

# o dicionário também pode ser percorrido lendo-se uma tupla de cada vez
» for t in tele:
»     print(t, tele[t] )

↳ ('Newton', 'Isaac') 1643
↳ ('Curie', 'Marie') 1867
↳ ('Einstein', 'Albert') 1879
↳ ('Hawking', 'Stephen') 1942

: Observe que, no exemplo acima, na criação de um par key : vale usando tuplas os parênteses ficam subentendidos:

» tele['Newton', 'Isaac'] = 1643
# é o mesmo que
» tele[('Newton', 'Isaac')] = 1643

# isso também ocorre quando fazemos
» a, b = 1, 2
# que é idêntico a
» (a, b) = (1, 2)

Se as chaves são strings simples elas podem ser especificadas como nomes de argumentos nomeados. No código abaixo uma lista de IDHs dos estados do sudeste é fornecida ao construtor.

» idh = dict(
»     SP=0.833,
»     RJ=0.832,
»     ES=0.802,
»     MG=0.800
» )
» idh
↳ {'SP': 0.833, 'RJ': 0.832, 'ES': 0.802, 'MG': 0.8}

Embora objetos de qualquer tipo (imutável) possam ser usados como chave, não existe um índice numérico associado aos elementos dos dicionários, além das próprias chaves. Dicionários mantém a ordem em que foram criados e sempre retornam o mesmo valor para cada chave.

# Não índices existem associados às chaves dos dicionários
» idh[1]
↳ KeyError: 1

» d = {0: 'a', 1: 'b', 2: 'c', 3: 'd'}
» d[3]  # operação possível porque 3 é uma chave, não um índice
↳ 'd'

Dicionários não são ordenados mas existe um objeto chamado OrderedDict que pode ser importado da classe collections que são.

O dicionário pessoa criado abaixo tem uma lista associada ao valor irmaos e outro dicionário associado ao valor filhos.

# o dicionário abaixo é inicializado vazio e preenchido de modo incremental
» pessoa = {}
» pessoa['nome'] = 'Edvaldo'
» pessoa['sobrenome'] = 'Santos'
» pessoa['idade'] = 41
» pessoa['profissao'] = 'dentista'
» pessoa['filhos'] = {'João':3, 'Ana':7, 'Marco':10 }
» pessoa['irmaos'] = ['Paulo', 'Eliane']

» pessoa
↳ {'nome': 'Edvaldo',
↳  'sobrenome': 'Santos',
↳  'idade': 41,
↳  'profissao': 'dentista',
↳  'filhos': {'João': 3, 'Ana': 7, 'Marco': 10},
↳  'irmaos': ['Paulo', 'Eliane']}

# pelo que vimos acima poderíamos também usar a forma
» pessoa['filhos'] = dict(João=3, Ana=7, Marco=10) 

A idade do filho que se chama Marco é pessoa['filhos']['Marco'] e o primeiro irmão listado é pessoa['irmaos'][0].

» pessoa['filhos']['Marco']
↳ 10
» pessoa['irmaos'][0]
↳ 'Paulo'
Você pode encontrar informação sobre Numpy: arrays e pandas: dataframes nesse site.

Naturalmente que uma estrutura muito complexa de vários itens aninhados pode ser difícil de manipular, portanto o bom senso deve prevalecer na construção desses objetos. Para esse fim existem objetos pre-programados bem mais sofisticados, como os arrays do Numpy e os dataframes do pandas. Também, como veremos em seções posteriores, podemos definir objetos do usuário com estruturas bem mais complexas que essas.

Os valores associdas às chaves são mutáveis, podem ser alterados após a criação do dicionário. Chaves e valores podem ser percorridos separadamente ou como tuplas.

» dic2 = {"nome" : "Pedro",
»         "sobrenome" : "Cabral",
»         "idade" : 27
»         }

# os valores são mutáveis
» dic2['sobrenome'] = 'Alves'
» dic2
↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 27}

Podem ser usados os métodos dos dicionários: dic.keys(), que retorna as chaves, dic.values(), que retorna os valores, e dic.itens(), que retorna as chaves e valores, todos eles como objetos iteráveis.

# as chaves podem ser lidas como um objeto iterável
» chaves = dic2.keys()
» chaves
↳ dict_keys(['nome', 'sobrenome', 'idade'])

» for chave in chaves:
»     print(dic2[chave], end=' ')
↳ Pedro Alves 27

# os valores podem ser lidos em um objeto iterável
» valores = dic2.values()
» valores
↳ dict_values(['Pedro', 'Alves', 27])

# percorrendo os valores
» for t in valores:
»     print(t, end=' ')
↳ Pedro Alves 27

# percorrendo as chaves
» for t in chaves:
»     print(t, end = ' ')
↳ nome sobrenome idade

# valores e chaves podem ser lidos como uma lista de tuplas
» itn = dic2.items()
» itn
↳ dict_items([('nome', 'Pedro'), ('sobrenome', 'Alves'), ('idade', 27)])

# os pares podem ser percorridos
» for (k,v) in dic2.items():
»     print(k,v)
↳ nome Pedro
↳ sobrenome Alves
↳ idade 27

O operador in verifica se um valor está presente entre as chaves ou valores.

# verificando se uma chave está no dicionário
» if 'idade' in dic2:
»     print('idade é uma das chaves')
↳ idade é uma das chaves

» print('peso é uma das chaves' if 'peso' in dic2 else 'valor não encontrado')
↳ valor não encontrado

# verificando se um valor está presente no dicionário
» print('Alves é um dos valores' if 'Alves' in dic2.values() else 'valor não encontrado')
↳ Alves é um dos valores

Diversos métodos são pré-programados com a classe dos dicionários. Vemos abaixo o uso de pop(), popitem() e copy(). Uma lista de métodos pode ser vista no final dessa seção.

# o método update serve para alterar o valor atribuído a uma chave
# e/ou inserir novos pares chave:valor
» dic2.update({'idade':19, 'sexo':'masc'})
» dic2
↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 19, 'sexo': 'masc'}

# um par chave valor pode ser inserido diretamente (como já fizemos acima)
» dic2['telefone'] = '21-991111110'
» dic2
↳ {'nome': 'Pedro',
↳  'sobrenome': 'Alves',
↳  'idade': 19,
↳  'sexo': 'masc',
↳  'telefone': '21-991111110'}

# um par pode ser removido pela chave
» dic2.pop('sexo')
» dic2
↳ {'nome': 'Pedro',
↳  'sobrenome': 'Alves',
↳  'idade': 19,
↳  'telefone': '21-991111110'}

# o método popitem() remove o último item inserido (após versão 3.7)
» dic2.popitem()
↳ ('telefone', '21-991111110')

# dic2 fica alterado
» dic2
↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 19}

# cópias de dicionários
# a atribuição abaixo atribui a dic3 o mesmo objeto que dic2
» dic3 = dic2
» dic3['idade'] = 67

# dic2 fica alterado
» dic2
↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 67}

# para criar cópia independente (criando um novo objeto) usamos
» dic4 = dic2.copy()
# ou, usando o instanciador do objeto
» dic4 = dict(dic2)

# agora dic4 pode ser alterado sem afetar dic2
» dic4['idade'] = 3
» dic2
↳ {'nome': 'Pedro', 'sobrenome': 'Alves', 'idade': 67}

No código acima vemos que a atribuição dic3 = dic2 simplesmente associa o mesmo objeto à variável dict3. Para gerar um novo objeto, que pode ser alterado independentemente, usamos o método dic4 = dic2.copy().

Dicionários podem ser aninhados ou seja, um dicionário pode conter outros dicionários como itens.

# o construtor pode receber diretamente os dicionários
» irmaos = {
»          1:{'nome':'Maria' , 'nasc':1989},
»          2:{'nome':'Marcos' , 'nasc':1991},
»          3:{'nome':'Marla' , 'nasc':2000},
»          }

# talvez seja mais legível definir separadamente (o que é equivalente)
» irmao1 ={'nome':'Maria' , 'nasc':1989}
» irmao2 = {'nome':'Marcos' , 'nasc':1991}
» irmao3 ={'nome':'Marla' , 'nasc':2000}
» irmaos = {1:irmao1, 2:irmao2,3:irmao3}

# em qualquer dos casos ficamos com o dicionário
» irmaos
↳ {1: {'nome': 'Maria', 'nasc': 1989},
↳  2: {'nome': 'Marcos', 'nasc': 1991},
↳  3: {'nome': 'Marla', 'nasc': 2000}}

# para acessar a data de nascimento do segundo irmão fazemos
» irmaos[2]['nasc']
↳ 1991
# lembrando que, nesse caso, 2 não é um índice mas a chave do dicionário.

Vamos usar um dicionário para contar quantas letras existem em uma palavra ou frase. Para isso criamos um dicionário vazio e iteramos pelas letras da palavra, usando a letra como índice e contagem como valor. A operação palavra.lower().replace(' ', '') transforma todas as letras em minúsculas e elimina espaços. set(palavra) pega as letras da palavra sem repetições. O método string.count(sub) conta quantas vezes a substring sub aparece na string.

# dicionário (contar quantas letras há em uma palavra)
» def letras(palavra):
»     ''' Recebe parametro palavra (string)
» 
»     Retorna dicionário {letra: contagem}
»     onde contagem é o número de letras = letra
»     Todas as letras são transformadas em minúscula
»     espaços são ignorados
»     '''
»     palavra = palavra.lower().replace(' ', '')
»     contagem = {}
»     for t in set(palavra):
»         contagem[t] = palavra.count(t)
»     return contagem

# quais e quantas letras
» dic = letras('Oftalmotorrinolaringologista') 
» print(dic)
↳ {'o': 6, 'f': 1, 't': 3, 'a': 3, 'l': 3, 'm': 1, 'r': 3, 'i': 3, 'n': 2, 'g': 2, 's': 1}

# quantas letras 't'?
» dic['t']
↳ 3

# espaços são ignorados, pois foram removidos
» print(letras('Rio de Janeiro'))
↳ {'r': 2, 'i': 2, 'o': 2, 'd': 1, 'e': 2, 'j': 1, 'a': 1, 'n': 1}

O método dictionary.get(chave, default) retorna o valor relativo à chave ou o valor default, caso a chave não seja encontrada.

» dic.get('t',0)
↳ 3
# não existe a letra 'b'
» dic.get('b',0)
↳ 0

Compreensão de dicionários: Dicionários podem ser construídos com compreensão:

» x = {i : i+2 for i in range(5)}
» x
↳ {0: 2, 1: 3, 2: 4, 3: 5, 4: 6}

# outro exemplo
» lista = ('a', 'céu de brigadeiro', 'de', 'prateado', 'laguna', 'introvertido', 'a' )
» dicio = {p : len(p) for p in lista}
» dicio
↳ {'a': 1,
   'céu de brigadeiro': 17,
   'de': 2,
   'prateado': 8,
   'laguna': 6,
   'introvertido': 12}

Métodos dos dicionários (dictionary)

Método Descrição
clear() remove todos os elementos do dictionário
copy() retorna uma cópia do dicionário
fromchaves() retorna dicionário com chaves e valores especificados
get() retorna o valor relativo a chave dada, ou valor default dado
items() retorna uma lista contendo uma tupla para cada par chave:valor
keys() retorna objeto iterável com as chaves do dicionário
pop() remove o elemento relativo à chave especificada
popitem() remove o último par chave:valor inserido
setdefault() retorna o valor relativo à chave dada. Se a chave não existe insere chave:valor
update() Atualiza o dicionário com pares chave:valor dados
values() retorna objeto iterável os valores do dicionário

Continue lendo: Arquivos e Pastas .

🔺Início do artigo

Bibliografia

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

Python: Testes, laços e funções


Testes lógicos e Laços

As linhas de código em um programa são executadas de cima para baixo, na ordem em que aparecem. Muitas tarefas podem ser executadas com esse esquema de coisas. No entanto a funcionalidade do código fica muito aumentada quando se insere os testes lógicos e laços que permitem a ramificação do fluxo de código sob certas condições.

if, elif, else

É possível alterar a ordem de execução de um programa usando testes e laços (loops). Para ver isso usaremos o comando input() que abre um caixa de interação com o usuário para ler um input de teclado. Em seguida um teste é realizado com a valor digitado pelo usuário, transformado em um inteiro, e ocorre uma ramificação na execução do código dependendo do valor inserido.

» i = int(input('Digite um número: '))
» if i > 10:
»    print(i, 'é maior que 10.')
» else:
»    print(i, 'não é maior que 10!')

↳ Digite um número: 6
↳ 6 não é maior que 10!

↳ Digite um número: 12
↳ 12 é maior que 10.

A sintaxe completa do teste lógico é:

if bool_1:
    execute_1
elif bool_2:
    execute_2
... outros elifs, se necessário
else:
    execute_3

Note que bool_1 e bool_2 devem ser valores booleanos (True ou False) ou expressões que são avaliadas como booleanos. As linhas execute_1 (2 ou 3) são quaisquer comandos ou sequência de comandos. Se bool_1 for True o código em execute_1 é executado e o teste é abandonado (nenhuma das demais linhas são executadas). Se bool_2 (após elif) for True o código em execute_2 é executado. Se nenhum dos testes resultar em True o código depois de else é executado. Tanto elif quanto else são opcionais.

No Python os blocos de código são definidos por indentação (espaços ou tabs), e não por chaves {}, como é mais comum em outras linguagens de programação. É costume se usar 4 espaços, e não tabulações para marcar esse escopo. Outro exemplo:

» i = int(input('Digite um número: '))
» if i < 10:
»    print(i, 'é menor que 10.')
» elif i < 20:
»    resposta = str(i) + 'é maior ou igual a 10 mas menor que 20.'
»    print(reposta)
» else:
»    print(i, 'é maior ou igual a 20.')

↳ Digite um número: 17
↳ 17 é maior ou igual a 10 mas menor que 20!

É importante notar que apenas o primeiro teste satisfeito é executado No caso, como o número digitado foi i = 17 o segundo teste foi satisfeito e as demais linhas ignoradas.

O operador ternário também é útil para gerar código sintético e de fácil leitura. Ele tem a seguinte sintaxe:
valor_1 if teste else valor_2. Ele retorna valor_1 se teste for verdadeira, valor_1 se falsa.

# operador ternário
» i = 3
» texto = 'MAIOR que 5' if i > 5 else 'MENOR ou IGUAL a 5'
» print(texto)
↳ MENOR ou IGUAL a 5

O mesmo teste admite outra sintaxe que é menos usada mas aparece em alguns códigos. Ela pode ajudar a tornar mais legível o código, dependendo da situação. Ela tem a seguinte forma:
(valor_0, valor_1)[teste], retornando valor_1 se teste == True ou valor_0 se teste == False.

» for i in range(5):
»     txt = str(i) + ' é ' + ('impar', 'par')[i % 2 == 0]
»     print(txt)
    
↳ 0 é par
↳ 1 é impar
↳ 2 é par
↳ 3 é impar
↳ 4 é par

No código acima o método str() transforma a variável de inteiro em texto para que possa ser concatenada com outras strings. O teste verifica se o resto da divisão de i por 2 é zero, ou seja, se i é par ou não.

Observe que o resultado acima é apenas uma aplicação do seguinte fato: True é avaliado como 1, False como 0.

» lista = ['valor_1','valor_2','outros_valores']
» print(lista[0], lista[1])
↳ valor_1 valor_2
» print(lista[False], lista[True])
↳ valor_1 valor_2    

O teste pode ser aninhado. No entanto, se o código ficar muito muito complexo ou difícil de ler, pode ser mais simples quebrar a operação em partes mais simples.

» for i in range(5,11):
»     txt = str(i) + (' é par', (' não é múltiplo de 3',' é múltiplo de 3')[i%3==0] )[i%2==1]
»     print(txt)
↳ 5 não é múltiplo de 3
↳ 6 é par
↳ 7 não é múltiplo de 3
↳ 8 é par
↳ 9 é múltiplo de 3
↳ 10 é par
# se não for par é retornado ('não é múltiplo de 3','é múltiplo de 3')[i%3==0]

Testes lógicos compostos podem ser implementados com o uso dos operadores lógicos and, or e not:

» x = 'oup'
» if x in 'Guarda' or x in 'roupas':
»     print('ok!')
↳ ok!

Intervalos podem ser testados de uma única vez. Por exemplo a < x ≤ b retorna True se x está no intervalo (a, b] (que exclui a e inclui b).

» for i in range(1, 8):
»     if 0 < i ≤ 3:
»         print(i, 'pertence [1, 3]')
»     elif 3 < i ≤ 6:
»         print(i, 'pertence [4, 6]')
»     else:
»         print(i, ' acima de 6')

↳ 1 pertence [1, 3]
↳ 2 pertence [1, 3]
↳ 3 pertence [1, 3]
↳ 4 pertence [4, 6]
↳ 5 pertence [4, 6]
↳ 6 pertence [4, 6]
↳ 7  acima de 6

Dicionários e decisões

Essa seção contém técnicas um pouco mais avançadas usando dicionários e funções. Se você não está familiarizado com elas pule para a seção Laços for e while. Você também pode consultar a seção sobre dicionários em Sequências e Coleções.

Quando existem muitos testes no código, principalmente quando valores individuais são verificados (em oposição à intervalos), pode ser uma boa técnica usar um dicionário como seletor de valores. Esse dicionário deverá conter os valores de testes nas chaves e retornos nos valores.

» # ao invés de fazer 10 testes
» for i in range(11):
»     if i==0:
»         print('zero')
»     elif i==1:
»         print('um')
»     elif i==2:
»         print('dois')
» # ... etc ... (truncado)
        
» # podemos fazer
» num_texto = {0:'zero', 1:'um', 2:'dois',3:'três', 4:'quatro',
»              5:'cinco',6:'seis',7:'sete',8:'oito',9:'nove'}
» for i in range(11):
»     print(i, num_texto.get(i,'não encontrado!'))
↳ 0 zero
↳ 1 um
↳ 2 dois
↳ 3 três
↳ 4 quatro
↳ 5 cinco
↳ 6 seis
↳ 7 sete
↳ 8 oito
↳ 9 nove
↳ 10 não encontrado!

Lembrando: O método de dicionário num_texto.get(i, default) procura pela chave i e retorna o valor correspondente. Se a chave não for econtrada o valor default é retornado.

Além disso um dicionário pode conter, em suas chaves, funções. Isso significa que podemos selecionar entre diversas funções sem usar seletores if.

# bloco 1
» dicio = {1: lambda x: x**2, 2: lambda x: x**3+4}
» print(dicio[1](3))
» print(dicio[2](3))
↳ 9
↳ 31

# bloco 2
» import math as m
» dicio2 = {1: m.sin, 2: m.exp}
» print(dicio2[1](3))
» print(dicio2[2](3))
↳ 0.1411200080598672
↳ 20.085536923187668

# bloco 3
» dicio = {'a': 'print("estou em a")', 'b': 'print("estou em b")'}
» eval(dicio['a'])
» eval(dicio['b'])
↳ estou em a
↳ estou em b

No bloco 1 o dicionário retorna funções anônimas (lambda):
dicio[1](3) é o mesmo que (lambda x: x**2)(3) = 3**2 =9.
No bloco 2 o dicionário retorna funções do módulo importado math:
dicio2[1](3) é o mesmo que math.sin(3) = 0.141...
No bloco 3 o dicionário retorna um texto que pode ser executado com eval:
dicio['a'] = 'print("estou em a")',
uma string, que quando executada como comando, imprime estou em a.

O código lambda x: x**2 corresponde à função matemática \( x \mapsto x^2\). As funções anônimas (lambda) serão vistas mais tarde nessas notas [ funções lambda].

Laços for e while


Outra estrutura útil de controle do fluxo de execução do código são os laços for. Eles servem para percorrer valores dentro de um objeto iterável.

# dentro do loop letra = P, y, t, h, o, n, sucessivamente
» for letra in 'Python':
»    if letra == 'y':
»        print('Chegamos na letra y')
↳ Chegamos na letra y

A variável letra assume os valores P, y, t, h, o, n sucessivamente mas apenas quando a letra é y uma mensagem é impressa. Observe os oito espaços abaixo do teste if para representar uma dupla indentação.

É comum que uma operação tenha que ser repetida um determinado número de vezes. Isso pode ser feito criando um objeto iterável e percorrendo seus valores.

# primeiro percorremos uma tupla
» for t in (1,3,5,7,9):
»    print(t, end=', ')
↳ 1, 3, 5, 7, 9,

# criamos uma 'faixa' de números (range) de 0 a 10, exclusive
» for t in range(10):
»     print(t, end=', ')
↳ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

Nos dois casos acima usamos o parâmetro end para a função print para que ela não insira uma quebra de linha após cada impressão, que é o comportamento default. Podemos usar a instrução else para inserir código a ser executado no final do loop.

» alunos = ['Martha', 'Renato', 'Paula']
» for aluno in alunos:
»      print(aluno, end=", ")
» else:
»      print("\b\b.\nTodos os nomes foram listados!")

↳ Martha, Renato, Paula. 
↳ Todos os nomes foram listados!

A instrução print("\b\b.\nTodos...") faz o retrocesso de dois caracteres, substitui a vírgula final por um ponto e pula para a proxima linha, antes de imprimir Todos….


Instruções break, continue e pass são usadas dentro de laços para alterar o fluxo de execução. break interrompe o laço, continue instrui pela continuação do laço e passserve apenas para marcar uma posição nas linhas de código, sem executar nenhuma tarefa. No exemplo abaixo o laço for percorre números de 0 até 99. O laço é abandonado quando num = 50.

» for num in range(100):
»     if num < 50:
»         continue
»     else:
»         break
» print(num)
↳ 50

Também são permitidos laços while que executam operações enquanto uma condição for verdadeira. No exemplo abaixo inicializamos a variável i = 1 e percorremos o laço enquanto i < 10. Dentro do laço a variável é impressa e incrementada de 1. O caracter especial “\n” serve para que uma quebra de linha seja inserida.

» i = 1
» while i < 10:
»     print(i, end=' ')
»     i += 1
» print('\nFim do loop. i =', i)
↳ 1 2 3 4 5 6 7 8 9 
↳ Fim do loop. i = 10

Não é raro criarmos um laço infinito a ser abandonado sob condição dada.

» num = 0
» while True:
»     num +=1
»     if num == 50:
»         print('cinquenta')
»     if num >= 100:
»         break
» print(num)
↳ cinquenta
↳ 100

Advertência importante: Sempre que se usa laços deve-se ter certeza de que o laço termina após um número finito de iterações. Caso contrário a execução do código entra em um loop infinito que pode exigir uma interrupção forçada. Por exemplo, o código abaixo entra em um loop infinito pois a variável t, dentro do loop, nunca assume o valor t=5, que é a condição de saída do laço.

» t = 0

» while t ! = 5:
»     t +=2
»     print(t, end='-')
↳ 2-4-6-8-10
# execução interrompida pelo usuário

Para interromper esse loop você pode apertar CTRL-C se estiver em sessão interativa do Python. No Jupyter Notebook aperte ■ na barra de ferramentas ou I-I com o cursor no modo de controle da célula.

Funções


Uma outra alternativa para alterar o fluxo de execução do código são as funções. Sempre que um bloco de código (um conjunto de operações) deve ser executado várias vezes esse bloco pode ser colocado dentro de uma função. As funções podem ser muito úteis para reaproveitamente do código dentro do módulo em que se está trabalhando ou mesmo em outros módulos diferentes. Funções são criadas e executadas da seguinte forma:

# definição
» def nome_da_funcao(argumentos):
»     operações # usando os argumentos
»     return valor
# para usar essa função
» nome_da_funcao(argumentos)

Nessa definição argumentos são um ou diversos objetos de quaisquer tipo que contém as informações que serão usadas no corpo da função. As operações podem conter instruções de qualquer tipo, tais como a impressão de texto, de leitura ou gravação de arquivos, ou o cálculo de valor a ser retornado pela função. A instrução return é opcional: se ela não for incluída a função retorna None.

» def minha_funcao(numero1, numero2):
»     resultado = numero1 ** numero2
»     return resultado

# para calcular 23
» minha_funcao(2,3)
↳ 8

# a chamada à função pode ser usada dentro de um cálculo
» minha_funcao(2,10) - 24
↳ 1000


Na figura ilustramos as partes de uma função. Parâmetros são as variáveis definidas e usadas no corpo da função. Os valores passados como parâmetros são os argumentos. No caso do uso da raiz quadrada seria necessária a importação da biblioteca math, na forma de from math import sqrt antes do uso.

Os argumentos (passados como parâmetros) das funções podem ter um valor default. Os parâmetros com valor default devem sempre ser inseridos por último, na definição da função.

» def outra_funcao(numero1, numero2 = 3):
»     return numero1 ** numero2

# se numero2 for omitida ela assume o valor default
» outra_funcao(6,)      # 63
↳ 216

# se numero2 for fornecida o valor default é sobrescrito
» outra_funcao(6,2)    # 62
↳ 36

Parâmetros podem ser usados como palavras chaves (keywords). Nesse caso eles podem aparecer em qualquer ordem, desde que nomeados durante a chamada à função.

» def funcao(a, b):
»     return 10 * a + b

# chamando a função sem usar palavras chaves
» funcao(10, 5)
↳ 105

# usando as palavras chaves
» funcao(b = 2, a = 11)
↳ 112

Finalmente, se pode passar um número arbitrário de argumentos passando um objeto iterável para a função. Isso é útil quando se escreve uma função que deve agir sobre um número desconhecido de argumentos. Isso é feito colocando-se um asterisco (*) antes do parâmetro.

» def somar(*numeros):
»     soma = 0
»     for num in numeros:
»         soma += num
»     return soma

# exemplo de uso
» somar(1,34,67,23,876)
↳ 1001

Recursividade

Função fatorial: para consolidar o conceito de função vamos considerar o exemplo de uma função para calcular o fatorial de um número inteiro. Por definição o fatorial de n é
$$
n! = \bigg\{ \begin{array}{lr}
1, & \text{ se n = 0,}\\
1 \times 2 \times 3 \times \cdots \times n, & \text{ se n > 0.}\\
\end{array}
$$

» def fatorial(n):
»     fat = 1
»     while n > 1:
»         fat *= n
»         n -= 1
»     return fat

» fatorial(11)
↳ 39916800

Lembrando, n -= 1 é o mesmo que n = n - 1 e fat *= n é fat = fat * n. O loop while só é executado se n = 2 ou maior. Dentro do loop a variável é decrementada de 1 e o produto armazenado em fat.

Uma definição mais elegante pode ser obtida usando recursividade. A função pode fazer uma chamada a si mesma.

» def fatorial_recursivo(n):
»     if n ≤ 1:
»         return 1
»     else:
»         return n * fatorial_recursivo(n-1)

» fatorial_recursivo(3)
↳ 6 

A mesma função pode ser definida de forma ainda mais compacta usando-se a estrutura valor1 if teste_booleano else valor2.

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

» print(fatorial(10))
↳ 3628800

No entanto, é muito comum que uma função de muito uso já esteja incluída em alguma biblioteca do Python, como é o caso da função fatorial que está na biblioteca math. Para usar um recurso que está em uma biblioteca externa ao núcleo básico do Python usamos import.

» import math
» math.factorial(11)
↳ 39916800

A série de Fibonacci é outro exemplo interessante. Essa é uma série de inteiros com muitas aplicações práticas. Ela é definida da seguinte forma: denotando o n-ésimo termo da série por \(f_n \) temos

$$f_0=1, f_1=1, f_n = f_{n-1} + f_{n-2} \text{ para } n \geq 2.$$

A série de Fibonacci tem muitas aplicações práticas interessantes

Portanto, cada número da série, à partir de n = 2 é a soma dos dois números anteriores. Por ex,: \(f_4 = f_3 + f_2 \).

Para calcular digamos, \(f_{10}\) podemos calcular todos os elementos da série até obter \(f_8\) e \(f_9\) e calcular sua soma. Alternativamente podemos definir uma soma recursiva.

» def fibonacci(n):
»     """ retorna o n-ésimo termo da série de Fibonacci """
»
»     if n in (0, 1):
»         return 1
»     else:
»         return fibonacci(n-1) + fibonacci(n-2)

» # para imprimir a série até um inteiro n
» def printFibonacci(n):
»     txt = ''
»     for t in range(n):
»         txt += '%d, ' % fibonacci(t)
»     print('Série de Fibonacci: { %s...}' % txt)

» printFibonacci(10)
↳ Série de Fibonacci: { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...}

Essa função também calcula todos os elementos anteriores recursivamente, até chegar em n=0 e n=1, o que termina a recursão, mas de um modo elegante e de fácil leitura. Esses procedimentos podem envolver grande quantidade de cálculo e memória. Tanto na série de Fibonacci quanto no calculo do fatorial um valor muito grande para argumento das funções pode travar o processador por falta de memória.

Observe que, da forma como esta função foi escrita para obter a série de Fibonacci, para calcular o n-ésimo termo é necessário calcular todos os termos anteriores. Uma forma forma mais rápida e eficiente pode ser obtida da seguinte forma:

» # para calcular os 10 primeiros termos da série de Fibonacci, retornando uma lista
» quantosTermos=10
» a, b = 0, 1
» fib = []
» for t in range(quantosTermos):
»     fib.append(b)
»     a, b = b, a+b

» print(fib)
↳ [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Nesse código usamos a função range(n) que retorna um objeto iterável (que pode ser percorrido um a um), de 0 até n-1. fib = [] inicializa uma lista vazia e fib.append(b) insere b nessa lista.

Gravando e importando funções

Podemos gravar em disco as funções e recuperá-las para uso no código. Suponha que gravamos o seguinte conteúdo em um arquivo com nome primo.py.

# arquivo primo.py    
def primo(n):
    """
    recebe um inteiro n
    retorna True se n é primo, caso contrário False
    """

    if n==0: return False        # uma condição especial para retornar 0, não primo
    primo = True
    for i in range(2, n//2):
        if (n % i) == 0:
            primo = False        # n é divisivel por i
            break
    return primo

No código acima procuramos por divisores de n de 2 até n//2 (metade inteira de n). Depois de gravado ele pode ser lido com import primo (o que importa todo o conteúdo do arquivo ou módulo) ou from primo import primo, que importa a função específica primo(). Depois podemos usá-la no codigo, com por ex.:

» from primo import primo
» while True:
»     n = int(input("Digite um número (0 para terminar)"))
»     if n==0: break
»     print(n, 'é primo' if primo(n) else 'não é primo')

Esse código inicia um laço que só termina quando o usuário digita 0. Se não for 0 ele testa se número é primo usando a função importa, e emite uma mensagem.

Digamos que digitamos 4 e 5:

Digite um número (0 para terminar) 4
4 não é primo
Digite um número (0 para terminar) 5
5 é primo

Docstrings

No Python você pode, e deve, inserir strings de documentação (docstrings) em suas funções para informar o que ela faz, que parâmetros recebe e que tipo dado ela retorna. Essa documentação pode ser consultada no momento em que se pretende usar a função. Docstrings também são inseridos em módulos, classes e métodos do Python.

A documentação de um objeto é definida inserindo-se uma string na primeira linha de sua definição, como um comentário.

def uma_funcao(parametros):
    ''' Essa função recebe parâmetros tal e tal; e retorna qual
    '''
    < corpo de função >
    return qual

Docstrings podem ser visualizadas com a propriedade __doc__ do objeto.

» def quadrado(n):
»     '''Recebe um número n, retorna n ao quadrado.'''
»     return n**2

» print(quadrado.__doc__)
↳ Recebe um número n, retorna n ao quadrado

Por convenção docstrings devem começar com uma letra maiúscula e terminar com um ponto e a primeira linha deve conter uma breve descrição da função. Caso existam mais de uma linha a segunda deve ficar em branco, separando o resumo do resto da descrição. As demais linhas devem descrever as convenções de chamada da função, os parâmetros e seu retorno. Ela também deve explicitar as exceções que podem ser lançadas na sua execução.

As funções pré definidas no python, assim como as importadas, podem igualmente ter suas docstrings consultadas. Docstrings também podem ser consultadas com a função help().

Abaixo um exemplo de uma docstring bem estruturada, e o uso de help.

» def soma_inteiros(i, j):
»     '''
»     Retorna a soma de dois inteiros.
» 
»             Parâmetros:
»                     i (int): inteiro
»                     j (int): inteiro
»             Retorna:
»                     string com texto: A soma i + j = soma.
»     '''
»     txt = 'A soma %d + %d = %d'% (i,j,i+j)
»     return txt

» soma_inteiros(45,54)

↳ 'A soma 45 + 54 = 99'

» help(soma_inteiros)

↳ Help on function soma_inteiros in module __main__:

  soma_inteiros(i, j)
      Retorna a soma de dois inteiros.
    
              Parâmetros:
                      i (int): inteiro
                      j (int): inteiro
    
              Retorna:
                      string com texto: A soma i + j = soma.

Além de documentar o código, doscstrings podem conter testes para as suas funções correspondentes, como veremos mais tarde.

No Jupyter Notebook a doscstring é exibida pressionando-se Shift-Tab após o nome da função ou objeto, o que pode ser feito para funções internas ou aquelas criadas pelo usuário.

Gerenciando erros (exceções)

Naturalmente cometemos erros quando escrevemos código. Nesses casos o python gera uma resposta que pode nos ajudar a corrigir o problema. No python erros são denominados exceções.

Suponha que executamos o seguinte código:

» x = input('Digite um número: ')
» y = x + 10
» print('O valor de y é %d' % y)
# o usuário insere o dígito 4 e obtém a resposta
↳ x = input('Digite um número: ')
 ----> y = x + 10
       print('O valor de y é %d' % y)
 TypeError: can only concatenate str (not "int") to str


Um erro de tipo foi lançado, TypeError pois o resultado do comando input é uma string (um texto) mesmo que o usuário tenha digitado ‘4’. O erro ocorreu na linha que tenta somar uma string a um número. Vemos que a resposta a um erro é útil e informa onde ele ocorreu. Muitas vezes são dadas sugestões de possíveis reformas ou até mesmo indicações para os manuais. No caso acima esse erro poderia ser resolvido com a inserção da conversão x = int(input('Digite um número: ')) que converteria a string ‘4’ em um dígito 4. Mas, o problema não estaria resolvido se o usuário resolver digitar ‘a’, por exemplo.

# a string 'a' não pode ser convertida em um inteiro
» int('a')
↳ ValueError: invalid literal for int() with base 10: 'a'

Esse tipo de problema pode ser contornado com as cláusulas try, except, else, finally. Coloque o comando onde um erro pode ocorrer dentro de uma cláusula try.

» saida = ''    
» x = input('Digite um número: ')
» try:
»     x = int(x)
» except:
»     saida = 'Ocorreu um erro.Digite um número, não letra.'
» else:
»     y = x + 10
»     saida = 'O valor da soma é %d' % y
» finally:
»     print(saida)

Havendo erro o fluxo de execução é transferido para except, caso contrário para else. Em qualquer dos casos o código após finally é executado.

# o usuário digita 4:
↳ Digite um número: 4
↳ O valor da soma é 14

# o usuário digita a:
↳ Digite um número: a
↳ Ocorreu um erro.Digite um número, não letra.

Existem muitos erros de execução pré-definidos no python. Suponha que exista uma função previamente definida, onde erros de alguns tipos podem ocorrer:

» try:
»     execute_alguma_coisa()
» except SyntaxError:
»     print('Ocorreu um erro de sintaxe')
» except TypeError:
»     print('Erro de tipo de variável!')
» except ValueError:
»     print('Um erro de valor!')
» except ZeroDivisionError:
»     print('Não é permitido dividir por zero!')
» else:
»     print('Relaxa, nada errado aconteceu!')
» finally:
»     print('Chegamos ao final...')    

Erros podem ser lançados a força pelo programador, para fins de depuração ou outro motivo qualquer.

» raise MemoryError("Acabou a memória")
↳ MemoryError: Acabou a memória

» raise ValueError("Erro de valor")
↳ ValueError: Erro de valor

Continue a leitura: Sequências e Coleções.

🔺Início do artigo

Bibliografia

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

Python: Strings


Strings

Strings são variáveis usadas para armazenar texto. No Python elas são sequências, coleções de caracteres ordenada por posição. A manipulação de strings é um dos pontos fortes do python e existem diversas funções para isso.

Strings são definidas com aspas simples (') ou aspas duplas ("). Aspas triplas (''' ou """) podem ser usadas para definir strings longas. Strings dentro de parentêses são concatenadas, o que é útil para se escrever uma string longa sem perder a legibilidade.

» string_1 = 'casa'
» string_2 = ' da mãe Joana'
» print(string_1 + string_2)
↳ casa da mãe joana

» string_longa = '''
             este é um
             exemplo de uma
             string longa!
             '''
» print(string_longa)
↳              este é um
               exemplo de uma
               string longa!

# string concatenada com parênteses (quebras de linhas são ignoradas)
» outra_longa = (
     'https://phylos.net/'
     '2020-12-16/'
     'pacote-factoranalyser/'
     )

» print(outra_longa)
↳  'https://phylos.net/2020-12-16/pacote-factoranalyser/'

Quando se usa aspas triplas, (''' ou """), os espaços e quebras de linha são mantidos na string final. Esse recurso é muito utilizado da documentação de módulos e funções, como veremos.  Já nas strings concatenadas com parênteses espaços e quebras de linha são removidos.

Vamos ver alguns exemplos de strings e de suas manipulações. Aqui usamos print(string, end=' ') para exibir a valor de string sem a quebra de linha que é default para essa função. Nesse caso a quebra é substituída por end=’ ‘, um espaço em branco.

# a sequência pode ser percorrida
» for letra in "banana":
»     print(letra, end=' ')
↳ b a n a n a 

# definimos uma variável de string
» palavra = 'Pindamonhangaba!'

# len() fornece seu comprimento (quantos caracteres)
» len(palavra)
↳ 16
# um caracter pode ser obtido por seu índice
» print(palavra[0], palavra[15])
↳ P !
# índices negativos contam do fim para o início
» print(palavra[-1])
↳ !
» print(palavra[-2])
↳ a

# palavra[-n] equivale a palavra[len(palavra)-n]  (desde que n < len(palavra))
» palavra[len(palavra)-3]   # palavra[-3]
↳ 'b'

# uma 'fatia' (slice)
» palavra[1:4]
↳ 'ind'

# se o segundo índice for omitido a fatia vai até o fim
» palavra[5:]
↳ 'monhangaba!'

# se o primeiro índice for omitido a fatia começa no início
» palavra[:5]
↳ 'Pinda'

» palavra[:]
↳ 'Pindamonhangaba!'

# len(palavra[i:j]) = j-i
» len(palavra[1:4])
↳ 3

Vimos, nos exemplos acima, que uma fatia (ou slice) palavra[i:f] se inicia em i e termina em f, exclusive. Os índices, e todas as contagens em python, se iniciam em 0. A posição final não é incluida de forma que len(palavra[i:j]) = j-i.

Os operadores + e * (adição e multiplicação) tem significado especial quando os operandos são strings. + promove a concatenação de strings e * retorna uma repetição.

# Operações com palavras
» p1 = 'casa'
» p2 = 'da mãe Joana'

# '+' faz uma concatenação
» p1 + ' ' + p2
↳ 'casa da mãe Joana'

# '*' repete a string
» p1 * 5
↳ 'casacasacasacasacasa'

Alguns operadores lógicos também são permitidos.

» palavra1 = 'teste'
» palavra2 = 'Teste'

» palavra1 == palavra2   # 2 sinais de =
↳ False

» palavra1 != palavra2
↳ True

» 't' in palavra1
↳ True

» 'y' not in palavra1
↳ True

Observe que nenhuma das operações realizadas até agora afetam o conteúdo da variável. Ao contrário, cada uma delas produz uma nova string. Dizemos que strings são imutáveis no python. Como são imutáveis, strings não podem ser alteradas in loco. No exemplo abaixo, para alterar ‘rato’ para ‘pato’ é necessário construir nova string e atribuí-la à variável.

# Strings são imutáveis
» str = 'rato'
» str[0]
↳ 'r'

# a tentativa de alterar a string resulta em erro
» str[0] = 'p'
↳ TypeError: 'str' object does not support item assignment

# para trocar 'r' por 'p'
» str = 'p' + str[1:]
» str
↳ 'pato'

Métodos das strings

Claro que o leitor não precisa se preocupar em memorizar todas essas funções. Uma primeira leitura serve para se ter conhecimento das possibilidades.

Como objetos, as strings possuem métodos e propriedades (coisas que veremos com detalhes mais tarde). A lista abaixo contém alguns métodos.

Método descrição
capitalize() converte 1º caracter em maiúsculo
casefold() converte string em minúsculas
center() retorna string centralizada
count() retorna número de ocorrências de um valor especificado na string
endswith() retorna True se string termina com valor especificado
find() busca por valor especificado na string e retorna a posição se encontrado
format() Formata de acordo com valores especificados (detalhes abaixo)
index() busca por valor especificado na string e retorna a posição se encontrado
isalnum() retorna True se todos os caracteres são alfa-numéricos
isalpha() retorna True se todos os caracteres são alfabéticos
isdecimal() retorna True se todos os caracteres são decimais
isdigit() retorna True se todos os caracteres são dígitos
islower() retorna True se todos os caracteres são minúsculos
isnumeric() retorna True se todos os caracteres são numéricos
isspace() retorna True se todos os caracteres são espaços
istitle() retorna True se a string segue regra de títulos
isupper() retorna True se todos os caracteres são maiúsculos
join() reune elementos de um iterável no final da string
ljust() retorna a string justificada à esquerda
lower() converte a string para minúsculas
lstrip() retorna a string sem espaços à esquerda
partition(“marca”) retorna tupla com parte anterior, “marca”, e parte posterior `a “marca”
replace(“a”, “b”) substitui trecho da string por outro especificado
rfind(“esse”) busca trecho especificado (“esse”, no ex.) e retorna sua última posição
rindex() busca trecho especificado value e retorna última posição
rjust() retorna string justificada à direita
rsplit() quebra a string no separador especificado, retornando lista
rstrip() retorna a string sem espaços à direita
split() quebra a string no separador especificado, retornando lista
splitlines() quebra a string nas quebras de linha, retornando lista
startswith() retorna True se string começa com valor especificado
strip() retorna a string sem espaços laterais
swapcase() inverte minúsculas e maiúsculas
title() converte em maiúscula o 1º caracter de cada palavra
upper() converte a string em maiúsculas
zfill() preencha com número de zeros especificado, no início

Alguns exemplos de uso são apresentados a seguir.

» str = 'uma palavra qualquer'
# tornar a primeira letra maiúscula
» str.capitalize()
↳ 'Uma palavra qualquer'

# contar quantas letras 'a' existem na string
» str.count('a')
↳ 5

# a string termina com 'r'?
» str.endswith('r')
↳ True

# em que posição 'qu' está na string
» str.find('qu')
↳ 12

# se o trecho procurado não existe na string
» str.find('y')
↳ -1

» str.isnumeric()
↳ False

» '1234567890'.isnumeric()
↳ True

» str.replace('palavra','coisa')
'uma coisa qualquer'

» str.startswith('un')
↳ False

# join é usada para concatenar strings
» uma_tupla = ('Carro', 'Casa', 'Gado')
» x = '-'.join(uma_tupla)
» print(x)
↳ Carro-Casa-Gado

# exemplo de uso para join, construindo uma tabela html
» html = '<table><tr><td>'
» html += '</td><td>'.join(uma_tupla)
» html += '</td></tr></table>'
» print(html)
↳ <table><tr><td>Carro</td><td>Casa</td><td>Gado</td></tr></table>

# lembrando que x = 'Carro-Casa-Gado'
» for txt in x.split('-'):
»     print(txt)
↳ Carro
  Casa
  Gado

# quebrando uma frase em palavras
» frase = 'A segunda palavra da frase'
» frase.split(' ')[1]
↳ 'segunda'

No exemplo abaixo str é quebrada nos espaços, pegando cada palavra em separado. O primeiro caso imprime a palavra que começa com ‘r’ e termina com ‘a’. O segundo imprime a palavra que começa com ‘p’ e não termina com ‘a’.

» str = 'pato pata rato rata'

» for palavra in str.split(' '):
»     if palavra.startswith('r') and palavra.endswith('a'):
»         print(palavra)
↳ rata

» for palavra in str.split(' '):
»     if palavra.startswith('p') and not palavra.endswith('a'):
»         print(palavra)
↳ pato

Existem alguns mecanismos de apoio à formatação de strings, particularmente útil na impressão de resultados. Os sinais especiais %d e %s podem ser usados na montagem de strings, o primeiro como substituto de um dígito, o segundo de texto. %f informa a substituição por número de ponto flutuante, sendo %.nf  um número de ponto flutuante com n casas decimais. %E indica número com notação científica (usando potências de 10).

» s1 = 'Uma string com um dígito e texto: dígito: %d; texto: %s' % (10, 'dez')
» print(s1)
↳ Uma string com um dígito e texto: dígito: 10; texto: dez

print('Um inteiro convertido em ponto flutuante: %f' % 5)
↳ Um inteiro convertido em ponto flutuante: 5.000000

print('Exibir o número %.02f com 2 casas decimais' % 25.123456)
↳ Exibir o número 25.12 com 2 casas decimais

print('Um número com notação científica: %E' % 6.789E7)
↳ Um número com notação científica: 6.789000E+07

Um método mais moderno de formatação consiste um usar string.format(), que permite código mais legível.

# marcadores nomeados são supridos por format
» txt1 = "Eu me chamo {nome} e tenho {idade} anos.".format(nome = 'João', idade = 36)
» print(txt1)
↳ Eu me chamo João e tenho 36 anos.

# os lugares podem ser marcados numericamente
» txt2 = "Moro em {0}, {1} há {2} anos.".format('Brasília', 'DF', 20)
» print(txt2)
↳ Moro em Brasília, DF há 20 anos.

# ou marcados apenas por sua ordem de aparecimento
» txt3 = "{} são convertidos em {}. Exemplo: {}.".format('Dígitos', 'strings', 100)
» print(txt3)
↳ Dígitos são convertidos em strings. Exemplo: 100.

# podemos controlar do número de casas decimais exibidos
» txt4 = 'Esse livro custa R$ {preco:.2f} com desconto!'.format(preco = 49.8)
» print(txt4) 
↳ Esse livro custa R$ 49.80 com desconto!

Também existe o chamado método de interpolação de strings que usa o prefixo f para que a string possa incorporar variáveis.

» nome = 'Albert'
» sobrenome = 'Einstein'
» ano = 1879
» print(f'O físico {nome} {sobrenome} nasceu em {ano}')
↳ O físico Albert Einstein nasceu em 1879

# podemos inserir operações dentro da string de formatação
» a = 214
» b = 3
» print(f'{a} multiplicado por {b} é {a * b}.')
↳ 214 multiplicado por 3 é 642.

Observe que strings justapostas são concatenadas. No entanto isso gera um código menos claro. O método de interpolação torna o código de mais fácil entendimento.

print('Paul ''Adrien ''Maurice ''Dirac' ' nascido em ' '1902')
Paul Adrien Maurice Dirac nascido em 1902

fisico = 'P. A. M. Dirac'
ano = 1902
print(f'O físico {fisico} nascem em {ano}' )
O físico P. A. M. Dirac nascem em 1902

Outros exemplos com métodos de strings

Seguem mais alguns exemplos de uso. Os outputs aparecem como comentários.

# um string de teste
original = 'Pedro, Paulo,   Jonas'

# split retorna uma lista com as partes
original.split(',')                      # ['Pedro', ' Paulo', '   Jonas']

# a compreensão de lista com strip para eliminar espaços
partes = [x.strip() for x in original.split(',')]
partes                                   # ['Pedro', 'Paulo', 'Jonas']

# join juntas as partes
' : '.join(partes)                       # 'Pedro : Paulo : Jonas'

# testes de pertencimento
'Jonas' in original     # True
'Jo' in original        # True
'X' in original         # False

original.index('lo')    # 10
original.index('X')     # ValueError substring not found

original.find('Pa')     # 7
original.find('Pu')     # -1

original.count('P')     # 2

original.replace('o','a')                # 'Pedra, Paula,   Janas'

Alguns exemplos ilustram melhor o método de string:
string.split(marca, [maxsplit])
sendo string o texto a ser partido, marca a marca (um string) onde o deve ser partido e maxsplit um parâmetro opcional que define em quantas partes o texto deve se partido. O método retorna uma lista.

» txt = "A casa da Amarildes"
# o default é marca = " " e maxsplit o número máximo de partes

» print(txt.split())
↳ ['A', 'casa', 'da', 'Amarildes']

# fazendo marca = "da"
» print(txt.split("da"))
↳ ['A casa ', ' Amarildes']

# fazendo marca = "a", nenhum split
» print(txt.split("a",0))
↳ ['A casa da Amarildes']

# fazendo marca = "a", 1 split
» print(txt.split("a",1))
↳ ['A c', 'sa da Amarildes']

# fazendo marca = "a", 2 splits (3 partes)
» print(txt.split("a",2))
↳ 'A c', 's', ' da Amarildes']

Outras operações mais sofisticadas com strings podem ser realizadas com expressões regulares. Sobre esse assunto você pode ler:

Caracteres de escape

Caracteres especiais podem ser inseridos em uma string por meio de “escapes”. Por exemplo, para usar aspas em uma string podemos fazer o seguinte:

# inserindo aspas simples em string
» print('O cara se chama O\'Maley')
↳ O cara se chama O'Maley

# se aspas duplas são usadas como delimitador a aspa simples pode ser usada
» print("O cara se chama O'Maley")
↳ O cara se chama O'Maley

» print("E seu primeiro nome é \"George\"")
↳ E seu primeiro nome é "George"

# quebra de linha
» print('linha1\nlinha2')
↳ linha1
↳ linha2
código significado
\’ aspas simples
\” aspas duplas
\\ barra invertida
\n nova linha
\r retorno de “carro”
\t tab, tabulação
\b backspace, volta 1 espaço
\f form feed
\ooo valor octal
\xhh valor hexadecimal
🔺Início do artigo

Continue a leituira: Python, testes, laços e funções

Bibliografia

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

Python: Introdução

Programação

Um computador é uma máquina eletrônica com capacidade para armazenar valores numéricos em formato binário e instruções de manipulação desses valores armazenados. Informações não numéricas, como letras, palavras e imagens, precisam ser convertidas em binários para que possam ser tratados. Nos primeiros tempos da computação a interação com as máquinas era muito difícil e um programador tinha que escrever códigos muito complexos, como diretamente em linguagem de máquina ou assembly. Hoje, após muito aperfeiçoamento, as linguagens modernas abstraem as camadas mais complexas de interação com o computador. O código tem a aparência de uma linguagem natural humana e pode ser lido quase como um texto comum.

Um exemplo pode ilustrar essa descrição. Considere que uma pessoa possui uma caixa cheia de bolas pretas e brancas. Ela quer saber qual é a porcentagem de bolas pretas, no total. Uma possível solução seria o seguinte procedimento, usando um bloco de notas para registrar bolas pretas e brancas:

  1. tire uma bola da caixa,
  2. verifique se é preta,
  3. se for preta faça uma marca para bolas pretas, caso contrário para as brancas,
  4. sem retornar a bola, verifique se ainda restam bolas na caixa,
  5. se ainda restam bolas, volte para a etapa (1),
  6. se acabaram as bolas, conte quantas marcas foram feitas para cada cor,
  7. calcule porcentagem = 100 * pretas/(pretas+brancas) e anote esse resultado.

Esse procedimento é chamado de algoritmo. A lista de tarefas é análoga à um programa de computador. As marcas feitas para contagem das bolas brancas e pretas são análogas às variáveis do programa.

Jupyter

O Python pode ser executado de várias formas diferentes. Existem bons editores e IDEs (Integrated Devolopment Environment) tais como o VSCode, Pycharm, Geany ou Spyder. Para esse tutorial usaremos o Jupyter Notebook. Para maiores informações sobre o Jupyter leia nesse site sobre sua instalação e execução.

Resumindo, sua instalação pode ser feita instalando-se o Anaconda:

  • Baixe a versão mais recente do Anaconda aqui. Existem instaladores gráficos para Linux, Windows e MacOS.
  • Instale seguindo as instruções na página de download ou contidas no arquivo instalador executável.
  • ou … usuários que conhecem Python e que preferem gerenciar seus pacotes manualmente, pode apenas usar:
    pip3 install jupyter.

Python

Python é uma linguagem de programação de alto nível, interpretada e de propósito geral, criada por Guido van Rossum em 1985 e em franco desenvolvimento desde então. Ela está disponível sob a licença GPL (GNU General Public License). Ela permite o uso interativo, com o usuário digitando as linhas de código e obtendo o resultado imediatamente, ou através de lotes (batches), com as linhas de código armazenadas em arquivos e executadas em grupo. Apesar de ser chamada de linguagem de script é possível criar aplicativos completos, na web ou para desktop, com interfaces gráficas modernas e eficientes. Além disso existe a possibilidade de gerar arquivos compilados e executáveis usando Cython.

Python Package Index

Uma grande quantidade de bibliotecas ou módulos que podem ser importados e executados nas sessões do python o tornam atraente para o gerenciamento de bancos de dados, de imagens, a mineração de dados, análise e plotagem sofisticada, inteligência artificial e aprendizado de máquina, além de várias outras aplicações. Você pode procurar por bibliotecas em The Python Package Index (PyPI).

Python é uma linguagem orientada a objetos (um conceito a ser estudado mais tarde) e todos de seus elementos são objetos. Seus conceitos básicos são de rápido aprendizado, apesar da existência das várias bibliotecas para uso específico que podem ser um pouco mais complexas, como ocorre com o pandas.

Variáveis e operações básicas

Convenção: Usaremos nesse texto a seguinte convenção: linhas iniciadas com » marcam o código inserido para execução. No Jupyter Notebook são as linhas de código dentro das células. marca os outputs, as linhas de resposta do código. Linhas, ou trechos de linha, iniciadas com # são comentários não executados.

# Essa linha é um comentário    
» x = 15        # inserindo um inteiro
» print(x)
↳ 15

Você pode usar o python para fazer operações matemáticas como um calculadora, embora isso raramente seja algo muito útil. Mais interessante é a possibilidade de criar variáveis que armazenam dados. No python uma variável é criada no momento em que se atribui a ela um valor, sem necessidade de informar de que tipo será (o que chamamos de tipagem dinâmica).

# inicializamos duas variáveis numéricas e uma com uma palavra
» x = 15           # um número inteiro
» y = 4.2          # um número de ponto flutuante
» z = 'Pedro'      # uma palavra ou 'string'

# para ver o conteúdo das variáveis podemos usar o método print
» print(x)
↳ 15
» print(y)
↳ 4.2
» print(z)
↳ Pedro

# podemos ver de que tipo são essas variáveis
» print(type(x))
↳ <class 'int'>

» print(type(y))
↳ <class 'float'>

» print(type(z))
↳ <class 'str'>

# variáveis podem ser apagadas com
» del x
» print(x)
↳ NameError: name 'x' is not defined 

O comando print() é uma função interna do python, e serve para exibir o conteúdo de seu parâmetro, no caso as variáveis x, y, z. Quando usamos o Jupyter Notebook uma variável ou expressão avaliada na última linha de uma célula é exibida quando a célula é executada, sem a necessidade de print.

Usamos type() para ver o tipo de conteúdo armazendo. Os tipos básicos do Python são:

Tipo Exemplos Nome no Python
Inteiros 1, -1, 67, -9900 int
Inteiros longos 123L, -999L long integer
Ponto flutuante 1.0, 3.14, 345.6, -99.9 double float
Complexos complex(1, 3), complex(“1+2j”), 1 + j complex
Booleano True, False bool
String ‘casa’, “teste de string” string, object

Com esses tipos básicos se constroi objetos mais complexos, como

Tipo Exemplos Nome no Python
Lista [‘a’, ‘b’, ‘c’, ‘d’], [1, 2, 3] list
Tupla (‘a’, ‘b’, ‘c’, ‘d’), (1, 2, 3) tuple
Dicionário {1: ‘um’, 2: ‘dois’} dictionary

Qualquer objeto do python pode ser associado a uma variável, que passa a ser uma representante ou “referência de acesso” a esse objeto. Os nomes de variáveis devem obedecer a algumas regras:

  • só podem conter números, letras e sublinhados (underscores): A até z, 0 até 9, _,
  • deve começar com uma letra ou sublinhado,
  • maiúsculas e minúsculas são consideradas caracteres diferentes (sensível ao caso).

A diferenciação de maiúsculas de minúsculas é válida em toda parte do Python. As variáveis idade, Idade, IDADE são três variáveis diferentes. A instrução if, para definir um ponto de bifurcação condicional no código, não ser escrita como If ou IF. Além disso existem as palavras reservadas (keywords) são nomes que fazem parte da sintaxe da linguagem e não podem ser utilizadas para nomes de variáveis:

Palavras reservadas do python:

and del for lambda raise
as elif from None return
assert else global nonlocal True
break except if not try
Class exec import or while
continue False in pass with
def finally is print yield

Como assumem o lugar dos objetos as variáveis podem ser usadas em lugar deles. Por ex., a operação abaixo faz um cálculo usando 2 variáveis e atribuindo o valor à uma terceira.

» largura = 2.20
» altura = 1.10
» area_retangulo = largura * altura
» print("A área de retângulo é: " , area_retangulo)
↳ A área de retângulo é: 2.42

Para facilitar a leitura posterior de um código os nomes de variáveis devem ser escolhidos de forma concisa e elucidativa de seu uso.

Os seguintes operadores matemáticos formam a base para as operações numéricas usuais.

Operador significado exemplo numérico
+ Adição x + y 24 + 36 = 60
Subtração x – y 36 – 24 = 12
* Multiplicação x * y 15 * 3 = 45
** Exponenciação x ** y 8 ** 3 = 512
/ Divisão x / y 15 / 8 = 1.875
// Divisão inteira x // y 15 // 8 = 1
% Módulo (resto da divisão inteira) x % y 15 % 8 = 7

Para verificar seu comportamento vamos fazer alguns testes.

# no python se pode declarar 2 variáveis (ou mais) simultaneamente †
» x, y = 3, 8
» x + y
↳ 11
» x - y
↳ -5
» x * y
↳ 24
» y ** x
↳ 512
» x / y
↳ 0.375
Estritamente dizendo, a atribuição x, y = 3, 8 é feita como em uma atribuição de tupla, (x, y) = (3, 8). Leia Sequências e Coleções.
# 3 dividido por 8 é 0, com resto 3 » x % y ↳ 0 » x // y ↳ 3

Terminologia: Um comando ou declaração (statement, em inglês) é uma linha com uma instrução única. Uma atribuição do tipo x = 1 armazena o valor 1 na variável x. No código abaixo temos 4 declarações. As 3 primeiras são atribuições e print() é uma função interna (predefinida) do Python:

» incremento = 0.2
» valor = 10
» valor = valor + incremento
» print(valor)
↳ 10.20

Várias declarações podem ser dispostas em uma única linha, separadas por ;. Por outro lado uma declaração longa pode ser quebrada em várias linhas.

# várias declarações em uma linha
» a=10; b=20; c=a*b; print(c)
↳ 200

# Uma string longa em várias linhas
» mensagem = "Olá.\nVocê está aqui para aprender a programar em Python.\n" \
»            "Leia o material e faça seus testes." \
»            "Indicar esse site para os seus amigos."

# Uma declaração longa em várias linhas 
» soma = 1 + 2 + 3 + 4 + \
»        5 + 6 + 7 + 8 + \
»        9 + 10
 
» print(mensagem)
↳ Olá.
  Você está aqui para aprender a programar em Python.
  Leia o material e faça seus testes. Indicar esse site para os seus amigos.

» print(soma)
↳ 55
# No texto acima usamos \n que é um código para quebra de linha. 

Diferente das operações matemáticas usuais, x = x + 1 significa some 1 ao valor de x e substitua o resultado em x.
Um atalho ou shortcut possível e muito usado é o seguinte:

# ao invés de escrever
» i = i + 1                  # podemos escrever, como o mesmo efeito
» i += 1                     # portanto
» valor = valor + incremento # pode ser escrito como
» valor += incremento

Outros operadores análogos podem ser usados.

Operador Exemplo Equivale a
+= Soma a += 2 a = a + 2
-= Subtração a -= 2 a = a – 2
*= Multiplicação a *= 2 a = a * 2
/= Divisão a /= 2 a = a / 2
%= Módulo a %= 2 a = a % 2
**= Expoente a **= 2 a = a ** 2
//= Divisão inteira a //= 2 a = a // 2

Além dos operadores matemáticos temos os operadores lógicos. Os resultados de operações lógicas são booleanos (True ou False) e eles são a base das decisões feitas em códigos. Alguns desses operadores podem ser usados, como veremos, em outros objetos que não apenas números.

Operador Significado Exemplo
> maior que x > y
>= maior ou igual a x ≥ y
< menor que x < y
>= menor ou igual a x ≤ y
== igual a x == y
!= diferente de x != y

Exemplos de uso de operações lógicas:

» num1 = 12
» num2 = 25

» print(num1 > num2)
↳ False

» print(num2 == num1 *2 +1)
↳ True

» string1 = 'isso'
» string2 = 'esse'
» print(string1 != string2)
↳ True

Execução do código em python


Foi sugerido o uso do Jupyter Notebook para o aprendizado do conteúdo nesse artigo. Isso permite interatividade e facilidade de instalação dos pacotes, bem como uma boa visualização das saídas de código. No entanto, muitas vezes é necessário rodar blocos grandes de código sem preocupação com resultados e saídas intermediárias.

Arquivos contendo comandos python e gravados com a extensão *.py podem ser executados em qualquer máquina que tenha o interpretador instalado. Por exemplo, suponha que temos na pasta de trabalho ativa o arquivo teste.csv e queremos alterar o seu nome. Usando um editor ASCII qualquer, como o Notepad do Windows, Geany ou Gedit no Linux e TextEdit no Mac, gravamos um arquivo com o nome de alterarNome.py, com o seguinte conteúdo:

#!/usr/bin/env python
import os
nome_antigo = 'teste.csv'
nome_novo = 'teste_novo.csv'
os.rename(nome_antigo, nome_novo)

Para executar esse arquivo abrimos uma sessão do terminal e digitamos:

» python alterarNome.py

A primeira linha contém o shebang (#!) com a instrução de onde está instalado o python em seu computador. Depois é importada a biblioteca os que contém comandos de interação com o sistema operacional. Se existir na pasta um arquivo teste.csv ele será renomeado para teste_novo.csv. Uma mensagem de erro será exibida se o arquivo teste.csv não existir.


Strings do Python

🔺Início do artigo

Bibliografia

Python.org

Livros

  • Barry, Paul: Head First Python, O’Reilly, Sebastopol, 2011.
  • Ceder, Vernon: The Quick Python Book, 2 ed., Manning Publications Co., Greenwich, 2010.
  • Downey, Allen: How to Think Like a Computer Scientist, Cambridge University Press, Nova Iorque, 2009.
  • Hall, Tim; Stacey, J-P: Python 3 for Absolute Beginners, Apress, Nova Iorque, 2009.
  • Hetland, Magnus Lie: Beginning Python, From Novice to Professional, Apress, Nova Iorque, 2005.
  • Lambert, Kenneth: Fundamentals of Python: From First Programs Through Data Structures, Cengage, Boston, 2010.
  • Lee, Kent D.: Python Programming Fundamentals, Springer-Verlag, Londres, 2011.
  • Lutz, Mark:Learning Python, 4ª Edição, O’Reilly, Sebastopol, 2005.
  • Payne, James: Beginning Python, Using Python 2.6 and Python 3.1, Wiley, Indianoplis, 2010.
  • Summerfield, Mark: Programming in Python 3, a complete introduction to the Python language, Pearson, Boston, 2010.

Recursos na internet

Pandas – Dataframes


Dataframes do pandas

Usamos a marcação:
» # linhas de comentários
» linhas de código (input)
↳ linhas de output

Um dataframe é uma forma de armazenar dados em forma tabular, como em uma planilha. O dataframe do pandas consiste em uma coleção de Series que são dispostas como suas colunas. A cada linha está associado um índice que serve para ordenar e selecionar dados. Como Series, cada coluna tem um tipo definido. No entanto, não é necessário que todas as colunas tenham o mesmo tipo e portanto dados de tipos diferentes podem ser armazenados.

O pandas usa muitos dos conceitos de programação do NumPy. A diferença principal entre eles está em que um array do NumPy usa dados homogêneos (todos do mesmo tipo) enquanto os dataframes do pandas podem conter dados de tipos diferentes.

Assim como o NumPy, muitas operações com dataframes levam em consideração o eixo ou axis. O default é axis = 0 (ou axis = 'index') o que indica operação sobre as linhas. axis = 1 (ou axis = 'column') indica operação realizada sobre as colunas.

O método mais comum de se criar um dataframe consiste em passar um dicionário e uma lista de índices para o construtor.

» import pandas as pd
» import numpy as np
» dados = {
            'nome': ['Pedro', 'Maria', 'Janaina', 'Wong', 'Roberto', 'Marco', 'Paula'],
            'cidade': ['São Paulo', 'São Paulo', 'Rio de Janeiro', 'Brasília',
                       'Salvador', 'Curitiba', 'Belo Horizonte'],
            'idade': [34, 23, 32, 43, 38, 31, 34],
            'nota': [83.0, 59.0, 86.0, 89.0, 98.0, 61.0, 44.0]
          }

» ids = [10, 11, 12, 13, 14, 15, 16]
» dfAlunos = pd.DataFrame(data=dados, index=ids)
↳ 

nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
12 Janaina Rio de Janeiro 32 86.0
13 Wong Brasília 43 89.0
14 Roberto Salvador 38 98.0
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0

No caso acima usamos um dict onde as chaves são os nomes dos campos ou colunas. À cada chave está associada uma lista cujos valores se tornam os valores das linhas, em cada coluna. A lista de índices foi fornecida separadamente. Se a lista ids não tivesse sido fornecida os índices do dataframe seriam inteiros, começando em 0.

Dataframes possuem a propriedade shape que contém as dimensões do objeto e os métodos head(n) e tail(n) que permitem, respectivamente, a visualização das n primeiras ou últimas linhas. Ao carregar um dataframe é sempre útil visualizar suas primeiras linhas e nomes de colunas. Também pode ser útil visualizar a matriz sob forma transposta, dada por dfAlunos.T.

» dfAlunos.shape
↳ (7, 4)
» # o que significa que temos 7 linhas, com 4 campos ou colunas.

» # para visualizar apenas as 2 primeiras linhas
» dfAlunos.head(2)
↳
nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
# para visualizar apenas as 2 últimas linhas
» dfAlunos.tail(2)
↳
nome cidade idade nota
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0
# A transposta:
» dfAlunos.T
↳
10 11 12 13 14 15 16
nome Pedro Maria Janaina Wong Roberto Marco Paula
cidade São Paulo São Paulo Rio de Janeiro Brasília Salvador Curitiba Belo Horizonte
idade 34 23 32 43 38 31 34
nota 83 59 86 89 98 61 44

Os nomes das colunas podem ser obtidos em uma lista, em um nome específico. Devemos nos lembrar que cada coluna do dataframe é uma Series. Portanto valem para elas os métodos e propriedades das Series.

» dfAlunos.columns
↳ Index(['nome', 'cidade', 'idade', 'nota'], dtype='object')

» # O nome da segunda coluna (lembrando que se conta a partir de 0)
» dfAlunos.columns[1]
↳ 'cidade'
» # Selecionando a coluna 'cidade'
» dfAlunos['cidade']
↳  10         São Paulo
   11         São Paulo
   12    Rio de Janeiro
   13          Brasília
   14          Salvador
   15          Curitiba
   16    Belo Horizonte
   Name: cidade, dtype: object

» # cada coluna é uma Series
» type(dfAlunos['cidade'])
↳ pandas.core.series.Series

» # os métodos das Series se aplicam
» dfAlunos['cidade'].value_counts()
↳  São Paulo         2
   Curitiba          1
   Rio de Janeiro    1
   Belo Horizonte    1
   Salvador          1
   Brasília          1
   Name: cidade, dtype: int64

» # valores únicos podem ser obtidos com unique()
» dfAlunos['cidade'].unique()
↳ array(['São Paulo', 'Rio de Janeiro', 'Brasília', 'Salvador', 'Curitiba',
         'Belo Horizonte'], dtype=object)

» # também podemos transformar esses valores em um set
» set(dfAlunos['cidade'])
↳ {'Belo Horizonte',
   'Brasília',
   'Curitiba',
   'Rio de Janeiro',
   'Salvador',
   'São Paulo'}


Observe que dfAlunos['cidade'] retorna uma Series, que é a coluna especificada do DF. Já o comando dfAlunos[['cidade']] retorna um dataframe com uma única coluna. É sempre importante saber com que tipo de objeto estamos lidando. Para isso podemos usar type() para conhecer esse tipo. Por exemplo, type(dfAlunos[['cidade']]) retorna pandas.core.frame.DataFrame . Observe que strings são listadas apenas como objects (sem discriminação de serem strings).

Também se pode usar a notação de ponto, dfAlunos.cidade, para obter a mesma coluna.

Como dissemos, o objeto DataFrame do pandas é formado por colunas que são Series, cada uma delas contendo elementos do mesmo tipo. As linhas podem, portanto, conter elementos de tipos diferentes. Para ver os tipos de cada coluna podemos examinar a propriedade dtype ou o método .info() que fornece uma visão geral sobre os dados, inclusive sobre a existência de valores nulos nos dados.

» dfAlunos.dtypes
↳ nome       object
  cidade     object
  idade       int64
  nota      float64
  dtype: object

» # Uma visão geral sobre os dados pode ser obtido com .info()
» dfAlunos.info()
↳ <class 'pandas.core.frame.DataFrame'>
  Int64Index: 7 entries, 10 to 16
  Data columns (total 4 columns):
   #   Column  Non-Null Count  Dtype
  ---  ------  --------------  -----
   0   nome    7 non-null      object
   1   cidade  7 non-null      object
   2   idade   7 non-null      int64
   3   nota    7 non-null      float64
  dtypes: float64(1), int64(1), object(2)
  memory usage: 600.0+ bytes

A descrição estatística dos campos numéricos é obtida com .describe() que fornece a contagem de itens, o valor médio, o desvio padrão, os quantis e máximos e mínimos. O método .corr() fornece o Coeficiente de Correlação de Pearson para todas as colunas numéricas da tabela. O resultado é um número no intervalo [-1, 1] que descreve a relação linear entre as variáveis.

# describe: resumo estatístico dos campos numéricos
» dfAlunos.describe()
↳
idade nota
count 7.000000 7.000000
mean 33.571429 74.285714
std 6.187545 19.661420
min 23.000000 44.000000
25% 31.500000 60.000000
50% 34.000000 83.000000
75% 36.000000 87.500000
max 43.000000 98.000000
» dfAlunos.corr()
↳ 
idade nota
idade 1.000000 0.564238
nota 0.564238 1.000000

Para acrescentar uma ou mais linhas (registros) ao dataframe podemos criar um novo dataframe com quantas linhas forem necessárias e concatená-lo com o antigo usando o método .concat().

» # criamos dataframe para a aluna Juliana e seus dados
» dfInserir = pd.DataFrame([('Juliana','Curitiba',28,80.0)],
                             columns=['nome','cidade','idade','nota'],
                             index=[100])
» pd.concat([dfAlunos, dfInserir])
↳
nome cidade idade nota
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0
12 Janaina Rio de Janeiro 32 86.0
13 Wong Brasília 43 89.0
14 Roberto Salvador 38 98.0
15 Marco Curitiba 31 61.0
16 Paula Belo Horizonte 34 44.0
100 Juliana Curitiba 28 80.0

Observe que o dataframe original não foi modificado. Caso se pretenda que modificação se torne permanente você deve atribuir o resultado retornado a uma novo (ou o mesmo) dataframe, como em dfAlunos = pd.concat([dfAlunos, dfInserir]).

Muitas vezes queremos que a novo dataframe criado ignore os índice das duas tabelas concatenadas. Nesse caso podemos ignorar os índices antigos e substituí-los por novos índices fornecidos, ou deixar que sejam preenchidos automaticamente.

» df = pd.concat([dfAlunos, dfInserir], ignore_index=True)
» df.index
↳ RangeIndex(start=0, stop=8, step=1)
» # os índices são inteiros de 0 até 8 (exclusive)

Uma nova coluna pode ser inserida, inclusive usando valores obtidos nas linhas. Na operação abaixo inserimos o campo calculado que é igual à multiplicação dos campos nota * idade, que não tem significado e é feito apenas como demonstração.

» dfAlunos['calculado']=dfAlunos['nota'] * dfAlunos['idade']
» dfAlunos
↳
nome cidade idade nota calculado
10 Pedro São Paulo 34 83.0 2822.0
11 Maria São Paulo 23 59.0 1357.0
12 Janaina Rio de Janeiro 32 86.0 2752.0
13 Wong Brasília 43 89.0 3827.0
14 Roberto Salvador 38 98.0 3724.0
15 Marco Curitiba 31 61.0 1891.0
16 Paula Belo Horizonte 34 44.0 1496.0

Como essa nova coluna não tem nenhum significado vamos apagá-la usando .drop().

# a operação seguinte retorna o dataframe sem a coluna 'calculado', mas não altera a original
» dfAlunos.drop(['calculado'], axis=1)
» # para alterar o dataframe usamos o parâmetro inplace=True
» dfAlunos.drop(['calculado'], axis=1, inplace=True)

Agora o dataframe tem a mesma estrutura de colunas original. Muitas operações do pandas retornam o resultado sobre o objeto sem alterá-lo. Algumas delas admitem o parâmetro inplace que, se True, faz a alteração do objeto in loco.

Para selecionar mais de uma coluna passamos uma lista com os nomes dos campos entre os colchetes.

» lista = ['nome','idade']
» # a linha abaixo é idêntica à dfAlunos[['nome','idade']]
» dfAlunos[lista]
↳
nome idade
10 Pedro 34
11 Maria 23
12 Janaina 32
13 Wong 43
14 Roberto 38
15 Marco 31
16 Paula 34


Podemos obter somas dos termos, tanto no sentido das linhas quanto das colunas, o que servirá como exemplo do uso do parâmetro axis. Relembrando:

axis = 0 (axis = ‘index’) opera sobre todas as linhas de cada coluna
axis = 1 (axis = ‘column’) opera sobre todas as colunas de cada linha

Para mostrar isso vamos construir um dataframe contendo apenas os dados numéricos, com os campos ‘idade’ e ‘nota’. Em seguida aplicamos sum(axis=0) para obter a soma das idades e notas, e sum(axis=1) para a soma
de cada linha.

» dfNumerico=dfAlunos[['idade', 'nota']]
» # a soma dos elementos em cada coluna
» dfNumerico.sum()   # o mesmo que dfNumerico.sum(axis=0)
↳ idade         235.0
  nota          520.0
  dtype: float64

» # a soma dos elementos em cada linha
» dfNumerico.sum(axis=1)
↳ 10    117.0
  11     82.0
  12    118.0
  13    132.0
  14    136.0
  15     92.0
  16     78.0
  dtype: float64

Importando um arquivo externo

É comum que os dados estejam inicialmente em forma de texto com os dados gravados em linhas e com valores separados por vírgula (um arquivo csv, comma separated values) ou outros separadores, tais como tabulação ou ponto e vírgula (;). Também ocorre que a exportação de outros aplicativos, como o Excel, possa ser feita nesse formato ou ser nele convertido.

Suponha que tenhamos no disco, na pasta de trabalho de sua sessão, um arquivo com o seguinte conteúdo:

    id, nome, cidade, idade, nota
    10, Pedro, São Paulo, 34, 83.0
    11, Maria, São Paulo, 23, 59.0
    12, Janaina, Rio de Janeiro, 32, 86.0
    13, Wong, Brasília, 43, 89.0
    14, Roberto, Salvador, 38, 98.0
    15, Marco, Curitiba, 31, 61.0
    16, Paula, Belo Horizonte, 34, 44.0

Não é importante que as colunas estejam bem alinhadas. Para importar esses dados para dentro de um dataframe usamos o método do pandas .read_csv(arq), onde arq é o nome completo do arquivo a ser lido (inclusive com seu caminho).

» dfNovoAlunos = pd.read_csv('./alunos.csv')
» dfNovoAlunos
↳
id nome cidade idade nota
0 10 Pedro São Paulo 34 83.0
1 11 Maria São Paulo 23 59.0
2 12 Janaina Rio de Janeiro 32 86.0
3 13 Wong Brasília 43 89.0
4 14 Roberto Salvador 38 98.0
5 15 Marco Curitiba 31 61.0
6 16 Paula Belo Horizonte 34 44.0

Vemos que o campo ‘id’ foi lido como um campo comum. Ele pode ser transformado em um pindice id efetivo por meio do método dataFrame.set_index:

» # torne o campo id o índice
» dfNovoAlunos.set_index('id', inplace=True)
» dfNovoAlunos.head(2)
↳ 
nome cidade idade nota
id
10 Pedro São Paulo 34 83.0
11 Maria São Paulo 23 59.0

Observe que o índice é apresentado na segunda linha do cabeçalho para indicar que não é um campo comum.

Alternativamente podemos ler o arquivo csv usando diretamente a primeira coluna como índice, informado pelo parâmetro index_col. Se o arquivo não contiver vírgulas separando os campos e sim outro sinal qualquer, como ; ou tabulações, passamos essa informação usando o parâmetro sep. Na última importação usamos url, a URL completa do arquivo, que pode estar em qualquer ponto disponivel da rede.

» # para usar a 1a coluna como índice
» dfNovoAlunos = pd.read_csv('./alunos.csv', index_col=0)
» # para ler arquivo em url, usando tab como separador
» dfOutroDF = pd.read_csv(url, sep='\t')

Vimos que, se nenhum argumento for passado, a primeira linha do arquivo é tomada como contendo os nomes (ou headers) das colunas. Para evitar isso passamos o parâmetro header = None. Nesse caso o nome das colunas é substituído por números.

Suponha que o arquivo nums.csv, com o conteúdo abaixo, esteja gravado no disco.

    11,12,13,14
    21,22,23,24
    31,32,33,34

Ele pode ser lido da seguinte forma:

» # informa que 1a linha não é header
» dfNone = pd.read_csv('./dados/nums.csv', header=None)

» # inserindo o nome ou labels para as colunas
» dfNames = pd.read_csv('./dados/nums.csv', names=('A', 'B', 'C', 'D'))

» # exibe os dois dataframes
» display('sem headers:', dfNone, 'com headers:', dfNames)
↳

‘sem headers:’

0 1 2 3
0 11 12 13 14
1 21 22 23 24
2 31 32 33 34

‘com headers:’

A B C D
0 11 12 13 14
1 21 22 23 24
2 31 32 33 34

Finalmente, se o cabeçalho contendo os títulos das colunas não está na primeira linha podemos passar o parâmetro header=n. A n-ésima linha será tomada como cabeçalho e todas as linhas anteriores serão ignoradas.

» dfPula2 = pd.read_csv('./dados/nums.csv', header=2)

Importação de csv com linhas de comprimentos desiguais

Pode ocorrer que um arquivo importado precise de alguma forma de tratamento para a montagem adequada de suas colunas, quando importado em um dataframe. Suponha, por exemplo, que temos em disco um arquivo de texto como o seguinte conteúdo:

palavras
a;1;2;3;6;7;9;10;17;121;131;138;252;463
aba;146
abafa;125;127;129
abaixado;1;125;127;130
abastecer;121;146;150;250;354;358
abatido;1;124;125;127;129;130;140;143;358;360
aberto;13;22;125;126;131;132;138;11;250;252
abismei;125;126
abra;14;22;125;126;131;132;138;11;250;252
abriga;125;126;131;137
acabrunha;143;150
acalmar;1;124;125;126;140;142;143;253
acaso;125;126;131;135;253

Essa é uma seleção muito pequena, para fim de demonstração da técnica, do extenso dicionário LIWC que associa palavras da língua portuguêsa à um conjunto de códigos associados à características linguísticas e cognitivas. Queremos classificar palavras de acordo com esse dataset.

Não podemos importar diretamente esses dados usando pd.read_csv(arquivo, sep=';') pois o comprimento irregular dos códigos geraria erro na importação. Então nos resta importar todas as linhas em uma coluna e tratá-las depois, de modo adequado.

# o arquivo csv está em    
arquivo='./dados/liwc.csv'
# importamos com separador que não existe no texto para obter apenas 1 coluna
dfPalavras = pd.read_csv(arquivo, sep='#')

dfPalavras
    palavras
0   a;1;2;3;6;7;9;10;17;121;131;138;252;463
1   aba;146
2   abafa;125;127;129
3   abaixado;1;125;127;130
4   abastecer;121;146;150;250;354;358
5   abatido;1;124;125;127;129;130;140;143;358;360
6   aberto;13;22;125;126;131;132;138;11;250;252
7   abismei;125;126
8   abra;14;22;125;126;131;132;138;11;250;252
9   abriga;125;126;131;137
10  acabrunha;143;150
11  acalmar;1;124;125;126;140;142;143;253
12  acaso;125;126;131;135;253

# cada linha pode se quebrada em ; retornando listas
dfPalavras.palavras.str.split(';').head(3)

0    [a, 1;2;3;6;7;9;10;17;121;131;138;252;463]
1                                    [aba, 146]
2                          [abafa, 125;127;129]

O próprio método split pode ser usado com os parâmetros str.split(sep, n, expand), que quebra a string em sep, usando apenas as primeiras n partes, e montando um dataframe se expand=True. Montamos um dataframe vazio df, separamos as linhas em 2 partes e as aplicamos em df, com as partes palavra e codigo separadas.

df = pd.DataFrame({})
df[['palavra','codigo']] = dfPalavras.palavras.str.split(';', n=1, expand=True)
# o seguinte dataframe é gerado
df
    palavra      codigo
0   a            1;2;3;6;7;9;10;17;121;131;138;252;463
1   aba          146
2   abafa        125;127;129
3   abaixado     1;125;127;130
4   abastecer    121;146;150;250;354;358
5   abatido      1;124;125;127;129;130;140;143;358;360
6   aberto       13;22;125;126;131;132;138;11;250;252
7   abismei      125;126
8   abra         14;22;125;126;131;132;138;11;250;252
9   abriga       125;126;131;137
10  acabrunha    143;150
11  acalmar      1;124;125;126;140;142;143;253
12  acaso        125;126;131;135;253

# podemos encontrar as palavras associadas ao codigo = 127
df[df.codigo.str.contains('127')]['palavra']
2       abafa
3    abaixado
5     abatido

# ou os codigos associados à palavra 'abafa'
df[df.palavra == 'abafa']['codigo'].values[0]
'125;127;129'

Na última linha df[df.palavra == 'abafa']['codigo'] é uma Series. Para extrair seu valor usamos .values[0]. Nesse caso o número de códigos é muito varíavel e não seria prático construir uma coluna para cada código.

Se for necessário ter colunas correspondendo à cada código teríamos que preencher os campos vazio com algum valor, como 0 ou NaN.

Gravando o dataframe em arquivos pickle

Ao términdo de cada fase de depuração, manipulação e ajustes do dataframe, podemos gravá-lo em disco em um arquivo pickle. O arquivo gravado pode ser lido e o dataframe recuperado em nova sessão de código, sem que tenhamos que repetir as etapas já realizadas.

» pd.to_pickle(dfNovoAlunos, './dados/Alunos.pkl')
» del dfNovoAlunos
» dfLido = pd.read_pickle('./dados/Alunos.pkl')

dfLido será um dataframe idêntico ao dfNovoAlunos gravado em etapa anterior. A pasta de destino deve existir ou uma exceção será lançada.

to_pickle Grava um objeto do pandas em arquivo pickled
read_pickle Ler arquivo pickle recuperando objeto
DataFrame.to_hdf Grava um objeto do pandas em arquivo HDF5
read_hdf Ler arquivo hdf recuperando objeto
DataFrame.to_sql Grava dataframe em um banco de dados sql
read_sql Ler arquivo sql recuperando objeto
DataFrame.to_parquet Grava dataframe em formato parquet binário.
read_parquet Ler arquivo parquet recuperando objeto
🔺Início do artigo

Bibliografia

Consulte bibliografia completa em Pandas, Introdução neste site.

Nesse site:

Introdução ao Pandas – Series

🔻Final do artigo

O que é pandas?

Pandas é uma biblioteca do Python, de código aberto e com licença BSD, desenvolvida e mantida pelo PуDаtа Dеvеlорmеnt Tеаm. Ela fornece ferramentas de manipulação e análise estatística de dados, capacidade de exibição gráfica E extração de dados análogos (mas não idênticos) aos de consultas sql.

A biblioteca foi construída com Cython e, por isso, é bastante rápida. Ela se destinava inicialmente ao uso no setor financeiro para análise de séries temporiais, tendo se tornado uma ferramenta de uso comum na manipulação de dados, particularmente em data science e machine learning. Ela tem sido usada para substituir as planilhas do Excel, para processar dados sob forma tabular, importando com facilidade dados de arquivos csv ou json.

Os experimentos abaixo foram realizados no Jupyter Notebook. Você encontra nesse site um artigo sobre instalação e uso do Jupyter Notebook. As linhas de código e suas respostas, quando existirem, serão representadas da seguinte forma:

» # Comentários (não são lidos ou executados pelo interpretador)
» Linha de input (entrada de comandos)
↳ Linha de output (resposta do código)

NumPy e matplotlib

NumPy é a abreviação de Numerical Python, a biblioteca base da computação numérica em Python. Ela fornece as estruturas de dados e algoritmos necessários para a maioria das aplicações científicas e de engenharia utilizando cálculo numérico. Entre outros objetos NumPy NumPy fornece

  • o objeto multidimensional ndarray onde se pode aplicar operações vetorializadas rápidas e eficientes,
  • um conjunto de funções para cálculos elementares com vetores e matrizes,
  • ferramentas de leitura e gravação de dados,
  • operações da álgebra linear, transformada de Fourier e geração de números aleatórios,
  • interação com C e C++.


Para dados numéricos as matrizes do NumPy são armazenadas e manipuladas de modo mais eficiente do que as demais estruturas do Python. Além disso códigos escritos em linguagens de baixo nível, como C ou Fortran, podem operar diretamente nos dados armazenados com o NumPy. Por isso muitas ferramentas de computação numérica do Python usam as matrizes NumPy como um estrutura de dados primária.

matplotlib é a biblioteca Python mais popular usada para a produção de gráficos e visualização de dados. Ela pode ser usada na geração de gráficos estáticos ou animados e visualização interativa.

Pandas

O pandas se utiliza basicamente de 3 objetos de armazenamento de dados com as seguintes estruturas:
Estrutura de dados dos objetos do pandas:

Nome dimensões tabela
Series 1D coluna (vetor)
DataFrame 2D tabela (matriz)
Panel 3D várias tabelas (matriz multidimensional)

As series e os dataframes são utilizados com maior frequência.

Como a manipulação de dados usando séries e dataframes frequentemente envolvem operações encontradas no módulo numpy é frequente sua importação junto com pandas.

» import pandas as pd
» import numpy as np

Series

Uma series é um objeto unidimensional, tipo um vetor, que contém uma sequência de objetos do mesmo tipo. A essa sequência está associado um outro vetor de labels chamado de index (índice). O método básico de criação de séries é da seguinte forma:
serie = pd.Series(data, index=index)
onde data pode ser um dict (um dicionário do Python), uma lista ou ndarray do numPy, ou um escalar. index é uma lista de índices que, se omitida, é preenchida com inteiros iniciando em 0.

» serie1 = pd.Series([-1, 9, 0, 2, 5])
↳  0   -1
   1    9
   2    0
   3    2
   4    5

À esquerda estão listados os índices que, por default, são uma lista de inteiros começando por 0. Os valores podem ser listados com .values e os índices com .index.

» serie1.values
↳ array([-1,  9,  0,  2,  5])

» serie1.index
↳ RangeIndex(start=0, stop=5, step=1)

Os índices podem ser inseridos manualmente e não precisam ser inteiros. O valor correspondente ao índice i pode ser acessado com serie[i], como mostrado abaixo, onde os índices são strings.

» serie2 = pd.Series([4, 7, -5, 3], index=['a', 'b', 'c', 'd'])
» serie2
↳ a    4
  b    7
  c   -5
  d    3
  dtype: int64

» serie2['c']
↳ -5

Uma série pode ser filtrada passando como índice outra série de elementos boolenos, (True, False). Além disso operações vetorializadas podem ser realizadas sobre todos os elementos da série.

# O teste seguinte gera uma série de booleanos
» serie2 > 3
↳ a     True
  b     True
  c    False
  d    False
  dtype: bool

# Essa serie de True e False filtra a serie original
» serie2[serie2 > 3]
↳ a    4
  b    7
  dtype: int64

# Operações podem, ser realizadas sobre todos os elementos
» serie2 * 3
↳ a    12
  b    21
  c   -15
  d     9
  dtype: int64

# o módulo Numpy possui a função exponencial
» np.exp(serie2)
↳ a      54.598150
  b    1096.633158
  c       0.006738
  d      20.085537
  dtype: float64

Séries se comportam, em muitos sentidos, como um dicionário. Uma série pode ser criada passando-se um dicionário como argumento para pd.Series().

» populacao = {
           'Sudeste': 89012240, 
           'Nordeste': 57374243,
           'Sul': 30192315,
           'Norte': 18672591,
           'Centro-Oeste':16504303
         }

» serie3 = pd.Series(populacao)

» serie3
↳ Sudeste         89012240
  Nordeste        57374243
  Sul             30192315
  Norte           18672591
  Centro-Oeste    16504303
  dtype: int64 


A ordem dos itens na série pode ser alterada através do fornecimento de uma lista com o ordenamento desejado para o argumento index. A elementos não presentes no dicionário serão atribuídos o valor NaN, Not a Number (não número). O método pd.isnull(serie) permite a avalição de quais elementos estão nulos ou NaN.

# fornecendo uma lista para o argumento index:
» ordem_alfabetica = ['Brasil', 'Centro-Oeste', 'Nordeste', 'Norte', 'Sudeste', 'Sul']
» serie4 = pd.Series(populacao, index=ordem_alfabetica)
» serie4
↳ Brasil               NaN
  Centro-Oeste    16504303
  Nordeste        57374243
  Norte           18672591
  Sudeste         89012240
  Sul             30192315
  dtype: int64

# para verificar quais valores são nulos (NaN)
» pd.isnull(serie4)
↳ Brasil           True
  Centro-Oeste    False
  Nordeste        False
  Norte           False
  Sudeste         False
  Sul             False
  dtype: bool

# os seguintes registros são NaN
» serie4[pd.isnull(serie4)]
↳ Brasil   NaN
  dtype: float64

» serie4[pd.notnull(serie4)]
↳ Centro-Oeste    16504303.0
  Nordeste        57374243.0
  Norte           18672591.0
  Sudeste         89012240.0
  Sul             30192315.0
  dtype: float64

» 'Brasil' in serie4
↳ True
» 'EUA' in serie4
↳ False
# uma excessão KeyError é lançada se o indice não existe
» serie4['EUA']
↳ KeyError

Como não existe no dicionário um valor para o índice Brasil a série atribuiu o valor NaN (Not a Number) para essa chave, a forma de NumPy e pandas indicar a ausência de um valor. O método retorna True ou False para cada item da série e pd.notnull() o seu inverso booleano. Alternativamente se pode usar o método das séries serie4.isnull().

Ainda sobre a semelhança entre séries e dicionários, podemos testar a existência de uma chave usando o operador in, como em 'Brasil' in serie4. A tentativa de recuperar um valor com índice não existente gera uma exceção (um erro do Python).

Observe que uma series tem propriedades de numpy ndarray, mas é um objeto de tipo diferente. Se um ndarray é necessário use series.to_numpy().

» type(serie4)
↳ pandas.core.series.Series

» type(serie4.to_numpy())
↳ numpy.ndarray

Series podem ser fatiadas com a notação serie[i:f] onde serão retornadas a i-ésima linha até a f-ésima, exclusive. Se i for omitido a lista se inicia em 0, se f for omitido ela termina no final da series.

» serie4
↳ Brasil                 NaN
  Centro-Oeste    16504303.0
  Nordeste        57374243.0
  Norte           18672591.0
  Sudeste         89012240.0
  Sul             30192315.0
  dtype: float64

» serie4[2:5]
↳ Nordeste        57374243.0
  Norte           18672591.0
  Sudeste         89012240.0
  dtype: float64

» serie4[:2]
↳ Brasil                 NaN
  Centro-Oeste    16504303.0
  dtype: float64

» serie4[4:]
↳ Sudeste    89012240.0
  Sul        30192315.0
  dtype: float64

Series podem ser exibidas com o método display(serie_0, serie_1, ..., serie_n). O resultado de operações envolvendo mais de uma serie, como a soma, alinha os valores por chaves (como uma UNION). Valores não presentes em um dos operandos terá NaN como resultado.

» serie5 = pd.Series([2, -1, -2, 1], index=['a', 'b', 'c', 'd'])
» serie6 = pd.Series([3, 4, 7, -1], index=['e', 'c', 'b', 'f'])
» display(serie5, serie6)
↳ a    2
  b   -1
  c   -2
  d    1
  dtype: int64

↳ e    3
  c    4
  b    7
  f   -1
  dtype: int64

» serie5 + serie6
↳ a    NaN
  b    6.0
  c    2.0
  d    NaN
  e    NaN
  f    NaN
  dtype: float64

Series possuem a propriedade name que pode ser atribuída na construção ou posteriormente com o método .rename(). No exemplo usamos o método np.random.randn(n) de numpy para fornecer um numpy.ndarray com n números aleatórios. Damos inicialmente a essa série o nome ‘randomica’, depois a renomeamos para ‘aleatoria’.

» serie7 = pd.Series(np.random.randn(5), name='randomica')
» serie7
↳ 0   -1.703662
  1    1.406167
  2    0.966557
  3   -0.557846
  4   -0.264914
  Name: randomica, dtype: float64

» serie7.name
↳ 'randomica'

» serie7= serie7.rename('aleatoria')
» serie7.name
↳ 'aleatoria'

O nome de uma série se torna seu índice ou nome de coluna caso ela seja usada para formar um DataFrame.

Atributos e Métodos das Series

Os atributos e métodos mais comuns e úteis das Series estão listados abaixo. Para uma lista completa consulte pandas.Series: API Reference.

Atributos

Atributo Descrição
at[n] Acesso ao valor na posição n
attrs Retorna ditionario de atributos globais da series
axes Retorna lista de labels do eixo das linhas
dtype Retorna o tipo (dtype) dos objetos armazenados
flags Lista as propriedades do objeto
hasnans Informa se existem NaNs
iat[n] Acesso ao valor na posição n inteiro
iloc[n] Acesso ao valor na posição n inteiro
index Retorna lista de índices
index[n] Retorna índice na n-ésima posição
is_monotonic Booleano: True se valores crescem de forma monotônica
is_monotonic_decreasing Booleano: True se valores decrescem de forma monotônica
is_unique Booleano: True se valores na series são únicos
loc Acessa linhas e colunas por labels em array booleano
name O nome da Series
nbytes Número de bytes nos dados armazenados
shape Retorna uma tuple com forma (dimensões) dos dados
size Número de elementos nos dados
values Retorna series como ndarray

Alguns casos de acessos à essas propriedades. Os outputs são exibidos como comentários:

» import pandas as pd
» import numpy as np 
» serie = pd.Series([1,-1, 2, 2, 6, 63])
» serie.size              # 6, o mesmo que len(serie)
» serie.at[5]             # 63
» serie.iloc[0]           # 1
» serie.index             # RangeIndex(start=0, stop=6, step=1)
» serie.is_monotonic      # False
» serie.is_unique         # False
» serie.shape             # (6,) 

Métodos

Nas tabelas abaixo a expressão “elemento a elemento” é abreviada para “e/e”. Estas operações são repetidas a cada elemento da, ou das, séries envolvidas. Por exemplo, o método serie.add(s2) é feita “elemento a elemento” (e/e):

» import pd	
» serie_a = pd.Series([-1,9,0,2, 5, -8])
» serie_b = pd.Series([1,-9,0, -2,-5, 8])
» serie_a.add(serie_b)
» # é o mesmo que
» serie_a + serie_b
» # que resulta em serie nula 
» # pd.Series([0,0,0,0,0,0])

Manipulação e gerenciamento das Series

Método (sobre série s, outra s2) Descrição
s.align(s2) Alinha 2 objetos em seus eixos usando método especificado
s.append(to_append[, ignore_index, …]) Concatena 2 ou mais Series
s.asof(where[, subset]) Último elemento antes da ocorrência de NaNs após ‘where’
s.backfill([axis, inplace, limit, downcast]) Aliás para DataFrame.fillna() usando method=’bfill’
s.bfill([axis, inplace, limit, downcast]) Alias para DataFrame.fillna() usando method=’bfill’
s.clip([min, max, axis, inplace]) Inclui apenas valores no intervalo
s.combine(s2, func[, fill_value]) Combina a s com s2 ou escalar, usando func
s.copy([deep]) Cópia do objeto s, índices e valores
s.drop_duplicates([keep, inplace]) Remove elementos duplicados de s
s.dropna() Remove valores faltantes de s
s.duplicated([keep]) Exibe valores duplicados na s
s.explode([ignore_index]) Transforma cada elemento de um objeto tipo lista em uma linha
s.fillna([value, method, axis, inplace, …]) Substitui valores NA/NaN usando método especificado
s.get(key) Retorna item correspondente à key
s.groupby([by, axis, level, as_index, sort, …]) Agrupa a s
s.head([n]) Retorna os n primeiros valores
s.interpolate([method, axis, limit, inplace, …]) Preenche valores NaN usando metodo de interpolação
s.item() Primeiro elemento dos dados como escalar do Python
s.items() Iteração (lazy) sobre a tupla (index, value)
s.iteritems() Iteração (lazy) sobre a tupla (index, value)
s.mask(cond[, s2, inplace, axis, level, …]) Substitui valores sob condição dada
s.max([axis, skipna, level, numeric_only]) Valor máximo
s.memory_usage([index, deep]) Memória usada pela s
s.min([axis, skipna, level, numeric_only]) Menor dos valores da s
s.nlargest([n, keep]) Retorna os n maiores elementos
s.nsmallest([n, keep]) Retorna os n menores elementos
s.nunique([dropna]) Retorna quantos elementos únicos existem na s
s.pad([axis, inplace, limit, downcast]) O mesmo que DataFrame.fillna() usando method=’ffill’
s.plot() O mesmo que pandas.plotting._core.PlotAccessor
s.pop(i) Remove s[i] de s e retorna s[i]
s.repeat(repeats[, axis]) Repete elementos da s
s.replace([to_replace, value, inplace, limit, …]) Substitui valores em to_replace por value
s.sort_values([axis, ascending, inplace, …]) Reorganiza s usando seus valores
s.str Usa funções de string sobre s (se string). Ex. s.str.split(“-“)
s.tail([n]) Últimos n elementos
s.unique() Retorna os valores da s, sem repetições
s.update(s2) Modifica s usando valores de s2, usando índices iguais
s.view([dtype]) Cria uma nova “view” da s
s.where(cond[, serie, inplace, axis, level, …]) Substitui valores se a condição cond = True

Operações matemáticas básicas:

s.ewm([com, span, halflife, alpha, …])Calcula exponencial com peso

s.abs() Retorna s com valor absoluto, e/e
s.add(s2) Soma s com s2, e/e
s.agg([func, axis]) Agrega usando func sobre o eixo especificado
s.apply(func[, convert_dtype, args]) Aplica func sobre os valores de s, e/e
s.div(s2) Divisão (float) de s por s2, e/e
s.divmod(s2) Divisão inteira e módulo de s por s2, e/e
s.dot(s2) Produto interno entre a s e s2
s.floordiv(s2) Divisão inteira da s por s2, e/e
s.mod(s2[, level, fill_value, axis]) Módulo de s por s2, e/e
s.rfloordiv(s2[, level, fill_value, axis]) Divisão inteira de s por s2, e/e
s.rmod(s2[, level, fill_value, axis]) Modulo da divisão da s por s2, e/e
s.rmul(s2[, level, fill_value, axis]) Multiplicação de s por s2, e/e
s.round([n]) Arredonda valores da s para n casas decimais.
s.rpow(s2[, level, fill_value, axis]) Exponential de s por s2, e/e
s.rsub(s2[, level, fill_value, axis]) Subtração da s por s2, e/e
s.rtruediv(serie[, level, fill_value, axis]) Divisão (float) de s por s2, e/e
s.sub(s2) Subtração de s por s2, e/e
s.subtract(serie) Idem
s.sum([axis, skipna, level, numeric_only, …]) Soma dos valores da s
s.transform(func[, axis]) Executa func sobre elementos de s
s.truediv(s2) Divisão (float) de s por s2, e/e
s.truncate([before, after, axis, copy]) Trunca a s antes e após índices dados
s.mul(s2[, level, fill_value, axis]) Multiplicação de s por s2, e/e
s.multiply(s2[, level, fill_value, axis]) Idem
s.pow(s2) Exponential de s por s2, e/e
s.prod([axis, skipna, level, numeric_only, …]) Produto dos elementos da s
s.product([axis, skipna, level, numeric_only, …]) Idem
s.rdiv(s2[, level, fill_value, axis]) Divisão (float) de s por s2, e/e
s.rdivmod(s2) Divisão inteira e módulo de s por s2, e/e

Operações estatísticas:

Método (sobre série s, outra s2) Descrição
s.corr(s2) Correlação de s com s2, excluindo NaNs
s.count([level]) Número de observações na s, excluindo NaN/nulls
s.cov(s2[, min_periods, ddof]) Covariância da s, excluindo NaN/nulls
s.cummax([axis, skipna]) Máximo cumulativo
s.cummin([axis, skipna]) Mínimo cumulativo
s.cumprod([axis, skipna]) Produto cumulativo
s.cumsum([axis, skipna]) Soma cumulativa
s.describe([percentiles, include, exclude, …]) Gera descrição estatística
s.kurt([axis, skipna, level, numeric_only]) Kurtosis imparcial
s.kurtosis([axis, skipna, level, numeric_only]) Idem
s.hist() Plota histograma da s usando matplotlib.
s.mad([axis, skipna, level]) Desvio médio absoluto dos valores de s
s.mean([axis, skipna, level, numeric_only]) Média dos valores
s.median([axis, skipna, level, numeric_only]) Mediana dos valores
s.mode([dropna]) Moda da s
s.quantile([q, interpolation]) Valor no quantil dado
s.ravel([order]) Retorna dados como um ndarray
s.sample([n, frac, replace, weights, …]) Amostra randomizada de items da s
s.sem([axis, skipna, level, ddof, numeric_only]) Erro padrão imparcial da média
s.skew([axis, skipna, level, numeric_only]) Inclinação imparcial
s.std([axis, skipna, level, ddof, numeric_only]) Desvio padrão da amostra
s.value_counts([normalize, sort, ascending, …]) Retorna s com a contagem de valores únicos
s.var([axis, skipna, level, ddof, numeric_only]) Variância imparcial dos valores da s

Operações com índices:

s.add_prefix('prefixo') Adiciona prefixo aos labels com string ‘prefixo’
s.add_suffix('sufixo') Adiciona sufixo aos labels com string ‘sufixo’
s.argmax([axis, skipna]) Posição (índice inteiro) do valor mais alto de s
s.argmin([axis, skipna]) Posição (índice inteiro) do menor valor de s
s.argsort([axis, kind, order]) Índices inteiros que ordenam valores da s
s.drop([labels]) Retorna s com labels removidos
s.first_valid_index() Índice do primeiro valor não NA/null
s.idxmax([axis, skipna]) Label do item de maior valor
s.idxmin([axis, skipna]) Label do item de menor valor
s.keys() Alias de index
s.last_valid_index() Índice do último valor não NA/null
s.reindex([index]) Ajusta a s ao novo índice
s.reindex_like(s2[, method, copy, limit, …]) Série com índices em acordo com s2
s.rename([index, axis, copy, inplace, level, …]) Altera o nome (labels) dos índices
s.reorder_levels(order) Reajusta níveis de índices usando order
s.reset_index([level, drop, name, inplace]) Reinicializa índices
s.searchsorted(value[, side, sorter]) Índices onde elementos devem ser inseridos para manter ordem
s.sort_index([axis, level, ascending, …]) Reorganiza s usando os índices

Testes, com retorno booleanos e comparações:

Método (sobre série s, outra s2) Descrição
s.all([axis, bool_only, skipna, level]) Booleano: se todos os elementos são True
s.any([axis, bool_only, skipna, level]) Booleano: se algum elemento é True
s.equals(s2) Booleano: True se s contém os mesmos elementos que s2
s.between(min, max) Booleano: satisfazendo min <= s <= max, e/e
s.compare(s2[, align_axis, keep_shape, …]) Compara s com s2 exibindo diferenças
s.eq(s2) Boleano: igualdade entre s e s2, e/e
s.ge(s2) Booleana: maior ou igual entre s e s2, e/e
s.gt(s2[, level, fill_value, axis]) Booleana: se s é maior que s2, e/e
s.isin(valores) Booleano: se elementos da s estão contidos em valores
s.isna() Booleano: se existem valores ausentes
s.isnull() Booleano: se existem valores nulos
s.le(s2) Booleana: se s é menor ou igual a s2, e/e
s.lt(s2[, level, fill_value, axis]) Booleana: se s é menor que s2, e/e
s.ne(s2[, level, fill_value, axis]) Booleana: se s é diferente de s2, e/e
s.notna() Booleana: se existem valores não faltantes ou nulos
s.notnull() Idem

Transformações para outros formatos e tipos:

s.astype(dtype[, copy, errors]) Transforma (cast) para dtype
s.to_clipboard([excel, sep]) Copia o object para o clipboard do sistema
s.to_csv([path_or_buf, sep, na_rep, …]) Grava a s como arquivo csv
s.to_dict() Converte s para dict {label ⟶ value}
s.to_excel(excel_writer[, sheet_name, na_rep, …]) Grava s como uma planilha Excel
s.to_frame([name]) Converte s em DataFrame
s.to_hdf(path_or_buf, key[, mode, complevel, …]) Grava s em arquivo HDF5 usando HDFStore
s.to_json([path_or_buf, orient, date_format, …]) Converte s em string JSON
s.to_latex([buf, columns, col_space, header, …]) Renderiza objeto para LaTeX
s.to_markdown([buf, mode, index, storage_options]) Escreve a s em formato Markdown (leia)
s.to_numpy([dtype, copy, na_value]) Converte s em NumPy ndarray
s.to_pickle(path[, compression, protocol, …]) Grava objeto serializado em arquivo Pickle
s.to_sql(name, con[, schema, if_exists, …]) Grava elementos em forma de um database SQL
s.to_string([buf, na_rep, float_format, …]) Constroi uma representação string da s
s.tolist() Retorna uma lista dos valores
s.to_list() idem

Operações com séries temporais:

s.asfreq(freq) Converte TimeSeries para frequência especificada.
s.at_time(time[, asof, axis]) Seleciona valores em determinada hora (ex., 9:30AM)
s.between_time(inicio, fim) Seleciona valores com tempo entre inicio e fim
s.first(offset) Seleciona período inicial de uma série temporal usando offset.
s.last(offset) Seleciona período final de uma série temporal usando offset

Alguns exemplos de uso dos métodos de pandas.Series:

» import pandas as pd
» import numpy as np
» serie = pd.Series([1,-1, 2, 2, 6, 63])
» serie.abs()
» # retorna  pd.Series([1, 1, 2, 2, 6, 63])

» # Muitos métodos não alteram a series inplace.
» s2 = serie.rename('NovoNome')  # não altera nome de serie
» s2.name
↳ 'NovoNome'
» # para alterar o nome usamos
» serie.rename('NovoNome', inplace=True)  # altera nome de serie inplace
» serie.name
↳ 'NovoNome'

» # um resumo estatístico pode ser visto com describe
» serie.describe()
↳ count    15.000000
  mean      4.866667
  std       3.044120
  min       0.000000
  25%       3.500000
  50%       5.000000
  75%       7.000000
  max       9.000000
  dtype: float64

» # gerando outra series
» data = np.random.randint(0, 10,size=15)
» serie2 = pd.Series(data)
» data
↳ array([2, 2, 6, 3, 1, 4, 3, 4, 3, 0, 8, 3, 8, 2, 7])

» serie.div(serie2)
↳ 0     2.500000
  1     0.000000
  ...
  9          inf
  13    0.000000
  14    1.000000
  dtype: float64


Observe que a divisão por 0 não gera erro mas é representada por inf.

Alguma habilidade gráfica pode ser encontrada entre os métodos das series. Um exemplo é o serie.hist() que traça o histograma da series usando matplotlib. Veremos com mais detalhes as funcionalidades dessa biblioteca.

» data = np.random.randint(0, 10,size=15)
» data
array([5, 0, 7, 7, 0, 9, 6, 5, 6, 9, 5, 5, 2, 0, 7])
» serie = pd.Series(data)
» serie.hist()

Objetos do pandas possuem métodos poderosos e eficientes. O método serie.str() permite operações de strings sobre os elementos da serie, se esses forem strings.

» str = ['-mercado','-tensão','-plasia']
» serie = pd.Series(str)
» serie
↳ 0    -mercado
  1     -tensão
  2     -plasia

» serie = serie.str.replace('-','hiper-')
» serie
↳ 0    hiper-mercado
  1     hiper-tensão
  2     hiper-plasia

» serie.str.split('-')
↳ 0    [hiper, mercado]
  1     [hiper, tensão]
  2     [hiper, plasia]

» # elementos não strings resultam em NaN
» serie = pd.Series([123,'-tensão','-plasia'])
» serie.str.replace('-','hiper-')
↳ 0             NaN
  1    hiper-tensão
  2    hiper-plasia

Series podem ser usadas na construção de dataframes, que serão vistos a seguir. Em particular o método to_frame() transforma uma series em um dataframe com uma coluna, onde cada valor ocupa uma linha.

» # uma series pode ser transformada em um dataframe
» df = serie.to_frame()
» # um dataframe é gerado e armazenado em df

Como a maioria dos métodos de series são análogos àqueles de dataframes faremos uma exploração mais extensa desses na sessão referente aos dataframes.

🔺Início do artigo

Bibliografia

  • Blair,Steve: Python Data Science, The Ultimate Handbook for Beginners on How to Explore NumPy for Numerical Data, Pandas for Data Analysis, IPython, Scikit-Learn and Tensorflow for Machine Learning and Business, edição do autor disponível na Amazon, 2019.
  • Harrison, Matt: Learning Pandas, Python Tools for Data Munging, Data Analysis, and Visualization,
    edição do autor, 2016.
  • McKinney, Wes: pandas: powerful Python data analysistoolkit, Release 1.2.1
    publicação do Pandas Development Team, revisado em 2021.
  • McKinney, Wes: Python for Data Analysis, Data Wrangling with Pandas, NumPy,and IPython
    O’Reilly Media, 2018.
  • Pandas: página oficial, acessada em janeiro de 2021.
  • Pandas User Guide, acessada em fevereiro de 2021.
  • Miller, Curtis: On Data Analysis with NumPy and pandas, Packt Publishing, Birmingham, 2018.

pandas e SQL comparados


SQL (Structured Query Language) é uma linguagem de programação de uso específico utilizada para consultar, extrair e gerenciar bancos de dados relacionais. Pandas é uma biblioteca do Python especializada para o tratamento e análise de dados estruturados, incluindo uma gama de formas de extrair dados.

Esse artigo faz uma comparação entre as consultas feitas a dataframes do pandas e as consultas SQL, explorando similaridades e diferenças entre os dois sistemas. Ele serve para descrever as funcionalidades de busca e edição do pandas e pode ser particularmente útil para aqueles que conhecem SQL e pretendem usar o pandas (ou vice-versa).

Para realizar os experimentos abaixo usamos o Jupyter Notebook, um aplicativo que roda dentro de um navegador, que pode ser facilmente instalado e permite a reprodução se todo o código aqui descrito. Você pode ler mais sobre Jupyter Notebook e Linguagem de Consultas SQL nesse site.

Esse texto é baseado em parte do manual do pandas e expandido. Ele usa um conjunto de dados baixados do github renomeado aqui para dfGorjeta. Nomes e valores dos campos foram traduzidos para o português.

# importar as bibliotecas necessárias
import pandas as pd
import numpy as np

url = "https://raw.github.com/pandas-dev/pandas/master/pandas/tests/io/data/csv/tips.csv"

dfGorjeta = pd.read_csv(url)
dfGorjeta.head()
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4

Para efeito de testar os comandos do dataframe vamos alterar os nomes dos campos e traduzir os conteúdos dos dados. Para descobrir quais são os valores dos campos, sem repetições, transformamos as séries em sets, uma vez que valores de um set (conjunto) não se repetem.

print(set(dfGorjeta["sexo"]))
print(set(dfGorjeta["fumante"]))
print(set(dfGorjeta["dia"]))
print(set(dfGorjeta["hora"]))
{‘Male’, ‘Female’}
{‘No’, ‘Yes’}
{‘Sat’, ‘Sun’, ‘Fri’, ‘Thur’}
{‘Lunch’, ‘Dinner’}

No código seguinte alteramos os nomes de campos e traduzimos o conteúdo. A sintaxe da operação de edição do dataframe será discutida mais tarde no artigo:

# muda os nomes dos campos
dfGorjeta.rename(columns={"total_bill":"valor_conta", "tip":"gorjeta",
                        "smoker":"fumante", "sex":"sexo","day":"dia",
                        "time":"hora","size":"pessoas"}, inplace=True)

# traduzindo os valores dos campos:
dfGorjeta.loc[dfGorjeta["fumante"] == "No", "fumante"] = "não"
dfGorjeta.loc[dfGorjeta["fumante"] == "Yes", "fumante"] = "sim"
dfGorjeta.loc[dfGorjeta["sexo"] == "Female", "sexo"] = "mulher"
dfGorjeta.loc[dfGorjeta["sexo"] == "Male", "sexo"] = "homem"
dfGorjeta.loc[dfGorjeta["hora"] == "Dinner", "hora"] = "jantar"
dfGorjeta.loc[dfGorjeta["hora"] == "Lunch", "hora"] = "almoço"
dfGorjeta.loc[dfGorjeta["dia"] == "Fri", "dia"] = "sex"
dfGorjeta.loc[dfGorjeta["dia"] == "Sat", "dia"] = "sab"
dfGorjeta.loc[dfGorjeta["dia"] == "Sun", "dia"] = "dom"
dfGorjeta.loc[dfGorjeta["dia"] == "Thur", "dia"] = "qui"

# Temos agora o seguinte dataframe
dfGorjeta
valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 1.01 mulher não dom jantar 2
1 10.34 1.66 homem não dom jantar 3
2 21.01 3.50 homem não dom jantar 3
3 23.68 3.31 homem não dom jantar 2
4 24.59 3.61 mulher não dom jantar 4
239 29.03 5.92 homem não sab jantar 3
240 27.18 2.00 mulher sim sab jantar 2
241 22.67 2.00 homem sim sab jantar 2
242 17.82 1.75 homem não sab jantar 2
243 18.78 3.00 mulher não qui jantar 2

As consultas SQL realizadas a seguir pressupõe a existência de um banco de dados com o mesmo nome, a mesma estrutura e dados que o dataframe dfGorjetas.

SELECT

Nas consultas SQL as seleções são feitas com uma lista de nomes de campos que se deseja retornar, separados por vírgula, ou através do atalho * (asterisco) para selecionar todas as colunas. No pandas a seleção de colunas é feita passando-se uma lista de nomes de campos para o DataFrame. Uma chamada ao dataframe sem uma lista de nomes de colunas resulta no retorno de todas as colunas, da mesma forma que usar * no SQL.

–– sql: consulta (query) usando select
SELECT valor_conta, gorjeta, fumante, hora FROM dfGorjeta LIMIT 5;
# pandas:
dfGorjeta[["valor_conta", "gorjeta", "hora"]].head()
valor_conta gorjeta hora
0 16.99 1.01 jantar
1 10.34 1.66 jantar
2 21.01 3.50 jantar
3 23.68 3.31 jantar
4 24.59 3.61 jantar

O método head(n) limita o retorno do dataframe às n primeiras linhas. n = 5 é o default. Para listar as últimas linhas usamos tail(n). Linhas também podem ser selecionadas por chamadas ao sei indice.

# Para acessar as últimas linhas do dataframe podemos usar
# dfGorjeta[["valor_conta", "gorjeta", "hora"]].tail()

# selecionando linhas por meio de seu índice.
dfGorjeta.iloc[[1,239,243]]
valor_conta gorjeta sexo fumante dia hora pessoas
1 10.34 1.66 homem não dom jantar 3
239 29.03 5.92 homem não sab jantar 3
243 18.78 3.00 mulher não qui jantar 2

Os dataframes possuem a propriedade shape que contém sua dimensionalidade. No nosso caso temos

dfGorjeta.shape
(244, 7)

o que significa que são 244 linhas em 7 campos.

No SQL você pode retornar uma coluna resultado de um cálculo usando elementos de outras colunas. No pandas podemos usar o método assign() para inserir uma coluna calculada:

–– sql:
SELECT *, gorjeta/valor_conta*100 as percentual FROM dfGorjeta LIMIT 4;
# pandas: método assign()
dfGorjeta.assign(percentual = dfGorjeta["gorjeta"] / dfGorjeta["valor_conta" *100]).head(4)
valor_conta gorjeta sexo fumante dia hora pessoas percentual
0 16.99 1.01 mulher não dom jantar 2 5.944673
1 10.34 1.66 homem não dom jantar 3 16.054159
2 21.01 3.50 homem não dom jantar 3 16.658734
3 23.68 3.31 homem não dom jantar 2 13.978041

Essa coluna é retornada mas não fica anexada ao dataframe. Para anexar uma coluna ao dataframe podemos atribuir o resultado do cálculo a uma nova coluna:

dfGorjeta["percentual"] = dfGorjeta["gorjeta"] / dfGorjeta["valor_conta"] * 100
print("Nessa estapa temos as colunas:\n", dfGorjeta.columns)

# Vamos apagar a coluna recém criada para manter a simplicidade da tabela
dfGorjeta.drop(["percentual"], axis=1, inplace=True)
Nessa estapa temos as colunas:
Index([‘valor_conta’, ‘gorjeta’, ‘sexo’, ‘fumante’, ‘dia’, ‘hora’, ‘pessoas’, ‘percentual’],
dtype=’object’)

WHERE


Filtragem de dados em consultas SQL são feitas através da cláusula WHERE. DataFrames podem ser filtrados de várias formas diferentes. O indexamento com valores booleanos é provavelmente o mais simples:

–– cláusula WHERE do sql
SELECT * FROM dfGorjeta WHERE hora = "jantar" LIMIT 5;
# filtragem por indexamento no pandas
dfGorjeta[dfGorjeta["hora"] == "jantar"].head(5)
valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 1.01 mulher não dom jantar 2
1 10.34 1.66 homem não dom jantar 3
2 21.01 3.50 homem não dom jantar 3
3 23.68 3.31 homem não dom jantar 2
4 24.59 3.61 mulher não dom jantar 4

A consulta acima funciona da seguinte forma:

# is_jantar é uma série contendo True e False (True para jantares, False para almoços)
is_jantar = dfGorjeta["hora"] == "jantar"
# usamos display para exibir a contagem de falsos/verdadeiros
display("Quantos jantares/almoços:", is_jantar.value_counts())

# para negar a série inteira, invertendo True ↔ False usamos ~ (til)
# a linha abaixo imprime o número de almoços na tabela
print("A lista contém %d almoços" % dfGorjeta[~is_jantar]["hora"].count())

# também podemos obter a lista das entradas que não correspondem a "jantar" usando
# dfGorjeta[dfGorjeta["hora"] != "jantar"]
‘Quantos jantares/almoços:’
True 176
False 68
Name: hora, dtype: int64A lista contém 68 almoços

Quando essa série é passada para o dataframe apenas as linhas correspondentes à True são retornados. A última consulta é equivalente à dfGorjeta[~is_jantar].head().

No SQL podemos procurar por partes de uma string com a cláusula LIKE. No pandas transformamos o campo dfGorjeta["sexo"]em uma string que possui o método startswith("string").

–– sql: SELECT TOP 2 sexo, valor_conta FROM dfGorjeta WHERE sexo LIKE 'ho%';
dfGorjeta.loc[dfGorjeta['sexo'].str.startswith('ho'),['sexo','valor_conta']].head(2)

que retorna as 2 primeiras linhas em que o campo sexo começa com o texto “ho”.

Também podemos procurar por campos que estão incluidos em um conjunto de valores:

–– sql:
SELECT * FROM dfGorjeta WHERE dia IN ('sab', 'dom');
dfGorjeta.loc[dfGorjeta['dia'].isin(["dom", "sab"])]

que retorna todas as linhas em que o campo dia é “dom” ou “sab”.

Assim como se pode usar operadores lógicos AND e OR nas consultas SQL para inserir múltiplas condições, o mesmo pode ser feito com dataframes usando | (OR) e & (AND). Por ex., para listar as gorjetas com valor superior à $5.00 dadas em jantares:

–– SQL: múltiplas condições em WHERE
SELECT * FROM dfGorjeta WHERE hora = 'jantar' AND gorjeta > 6.00;
# no pandas
dfGorjeta[(dfGorjeta["hora"] == "jantar") & (dfGorjeta["gorjeta"] > 6.00)]
valor_conta gorjeta sexo fumante dia hora pessoas
23 39.42 7.58 homem não sab jantar 4
59 48.27 6.73 homem não sab jantar 4
170 50.81 10.00 homem sim sab jantar 3
183 23.17 6.50 homem sim dom jantar 4
212 48.33 9.00 homem não sab jantar 4
214 28.17 6.50 mulher sim sab jantar 3

Podemos obter uma lista dos dados correspondentes a gorjetas dadas por grupos com 5 ou mais pessoas ou com contas de valor acima de $45.00, limitada aos 4 primeiros registros:

–– SQL:
SELECT * FROM dfGorjeta WHERE pessoas >= 5 OR valor_conta > 45 LIMIT 4;
# pandas
dfGorjeta[(dfGorjeta["pessoas"] >= 5) | (dfGorjeta["valor_conta"] > 45)].head(4)
valor_conta gorjeta sexo fumante dia hora pessoas
59 48.27 6.73 homem não sab jantar 4
125 29.80 4.20 mulher não qui almoço 6
141 34.30 6.70 homem não qui almoço 6
142 41.19 5.00 homem não qui almoço 5

Dados ausentes são representados por NULL no, uma marca especial para indicar que um valor não existe no banco de dados. Nos dataframes do pandas o mesmo papel é desempenhado por NaN (Not a Number). Esses marcadores podem surgir, por ex., na leitura de um arquivo csv (valores separados por vírgulas) quando um valor está ausente ou não é um valor numérico em uma coluna de números. Para verificar o comportamento do pandas com NaN criamos um dataframe com valores ausentes. Verificações de nulos é feita com os métodos notna() e isna().

frame = pd.DataFrame({"col1": ["A", "B", np.NaN, "C", "D"], "col2": ["F", np.NaN, "G", "H", "I"]})
frame
col1 col2
0 A F
1 B NaN
2 NaN G
3 C H
4 D I

Se temos um banco de dados SQLcom essa estrutura e conteúdo podemos extrair as linhas onde col2 é NULL usando a consulta:

–– sql
SELECT * FROM frame WHERE col2 IS NULL;
# no case do pandas usamos
frame[frame["col2"].isna()]
col1 col2
1 B NaN

De forma análoga, podemos extrair as linhas para as quais col1 não é NULL. No pandas usamos notna().

–– sql
SELECT * FROM frame WHERE col1 IS NOT NULL;
# pandas: linhas em que col1 não é nula
frame[frame["col1"].notna()]
col1 col2
0 A F
1 B NaN
3 C H
4 D I

GROUP BY


No SQL consultas com agrupamentos são feitas usando-se as operações GROUP BY. No pandas existe o método groupby() que tipicamente particiona o conjunto de dados em grupos e aplica alguma função (em geral de agregamento), combinando depois os grupos resultantes.

Um exemplo comum é o de particionar os dados em grupos menores e contar os elementos desses grupos. Voltando ao nosso dataframe dfGorjeta podemos consultar quantas gorjetas foram dadas por grupos de cada sexo:

–– sql
SELECT sexo, count(*) FROM dfGorjeta GROUP BY sexo;
# o equivalente em pandas seria
dfGorjeta.groupby("sexo").size()
sexo
mulher 87
homem 157
dtype: int64

O resultado é uma series cujos valores podem ser retornados por seu nome de index ou pelo número desse indice.

print("A lista contém %d homens" % dfGorjeta.groupby("sexo").size()[0])
print("\t\t e %d mulheres" % dfGorjeta.groupby("sexo").size()["mulher"])
A lista contém 157 homens
e 87 mulheres

É possível aplicar o método count() para cada coluna, individualmente:

dfGorjeta.groupby("sexo").count()
valor_conta gorjeta fumante almoço hora pessoas
sexo
mulher 87 87 87 87 87 87
homem 157 157 157 157 157 157

Observe que no código do pandas usamos size() e não count(). Isso foi feito porque o método count() é aplicado sobre cada coluna e retorna tantos valores quantas colunas existem, com valores não null.

Também se pode aplicar o método count() para uma coluna específica:

# para contar valores em uma única coluna primeiro ela é selecionada, depois contada
dfGorjeta.groupby("sexo")["valor_conta"].count()
sexo
mulher 87
homem 157
Name: valor_conta, dtype: int64

Existem diversas funções de agregamento. São elas:

função descrição
mean() calcula médias para cada grupo
sum() soma dos valores do grupo
size() *tamanhos dos grupos
count() número de registros no grupo
std() desvio padrão dos grupos
var() variância dos grupos
sem() erro padrão da média dos grupos
describe() gera estatísticas descritivas
first() primeiro valor no grupo
last() último valor no grupo
nth() n-ésimo valor (ou subconjunto se n for uma lista)
min() valor mínimo no grupo
max() valor máximo no grupo

* A função size() retorna o número de linhas em uma serie e o número de linhas × colunas em dataframes.

Para obter um resumo estatístico relativo ao campo gorjeta, agrupado pelo campo sexo podemos usar:

dfGorjeta.groupby("sexo")["gorjeta"].describe()
count mean std min 25% 50% 75% max
sexo
homem 157.0 3.089618 1.489102 1.0 2.0 3.00 3.76 10.0
mulher 87.0 2.833448 1.159495 1.0 2.0 2.75 3.50 6.5

Múltiplas funções podem ser aplicadas de uma vez. Suponha que queremos determinar como os valores das gorjetas variam por dia da semana. O método agg() (de agregar) permite que se passe um dicionário para o dataframe agrupado, indicando que função deve ser aplicada a cada coluna.

–– sql (agrupe os dados por dia, calcule a média para cada dia e o número de entradas contadas)
SELECT dia, AVG(gorjeta), COUNT(*) FROM dfGorjeta GROUP BY dia;
# na pandas, use mean no campo gorjeta, size no campo dia
dfGorjeta.groupby("dia").agg({"gorjeta": np.mean, "dia": np.size})
gorjeta dia
dia
dom 3.255132 76
qui 2.771452 62
sab 2.993103 87
sex 2.734737 19

Também é possível realizar o agrupamento por mais de uma coluna. Para fazer isso passamos uma lista de colunas para o método groupby().

–– agrupe primeiro por "fumante", depois por "dia"
–– realize a contagem dos registros e a média das gorjetas
SELECT fumante, dia, COUNT(*), AVG(gorjeta) FROM dfGorjeta GROUP BY fumante, dia;
# no pandas
dfGorjeta.groupby(["fumante", "dia"]).agg({"gorjeta": [np.size, np.mean]})
gorjeta
size mean
fumante dia
não dom 57.0 3.167895
qui 45.0 2.673778
sab 45.0 3.102889
sex 4.0 2.812500
sim dom 19.0 3.516842
qui 17.0 3.030000
sab 42.0 2.875476
sex 15.0 2.714000

JOIN

No SQL tabelas podem ser juntadas ou agrupadas através da cláusula JOIN. Junções podem ser LEFT, RIGHT, INNER, FULL. No pandas se usa os métodos join() ou merge(). Por defaultjoin() juntará os DataFrames por seus índices. Cada método tem parâmetros que permitem escolher o tipo da junção (LEFT, RIGHT, INNER, FULL), ou as colunas que devem ser juntadas (por nome das colunas ou índices). [Linguagem de Consultas SQL]

# para os exercícios que se seguem criamos os dataframes
df1 = pd.DataFrame({"key": ["A", "B", "C", "D"], "value":  [11, 12, 13, 14]})
df2 = pd.DataFrame({"key": ["B", "D", "D", "E"], "value":  [21, 22, 23, 24]})
# para exibir esses dataframes com formatação usamos display()
display(df1)
display(df2)
key value
0 A 11
1 B 12
2 C 13
3 D 14
key value
0 B 21
1 D 22
2 D 23
3 E 24

Como antes supomos a existência de duas tabelas de dados sql como as mesmas estruturas e dados para considerarmos as várias formas de JOINs.

INNER JOIN

–– junção das duas tabelas ligadas por suas chaves - key
SELECT * FROM df1 INNER JOIN df2 ON df1.key = df2.key;
# por default merge() faz um INNER JOIN
pd.merge(df1, df2, on="key")
key value_x value_y
0 B 12 21
1 D 14 22
2 D 14 23

O método merge() também oferece parâmetros para que sejam feitas junções de uma coluna de um dataframe com o índice de outro dataframe. Para ver isso vamos criar outro dataframe a partir de df2, usando o campo key como índice.

# novo dataframe tem campo "key" como índice
df2_indice = df2.set_index("key")
display(df2_indice)
pd.merge(df1, df2_indice, left_on="key", right_index=True)
value
key
B 21
D 22
D 23
E 24
key value_x value_y
1 B 12 21
3 D 14 22
3 D 14 23

LEFT OUTER JOIN

A junção LEFT OUTER JOIN recupera todos as campos à esquerda, existindo ou não uma linha correspondente à direita. O parâmetro how="left" é o equivalente no pandas.

–– sql: recupera todos os valores de df1 existindo ou não correspondente em df2
SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="left" equivale a LEFT OUTER JOIN
pd.merge(df1, df2, on="key", how="left")
key value_x value_y
0 A 11 NaN
1 B 12 21
2 C 13 NaN
3 D 14 22
4 D 14 23

Observe que df2 não possui campos com key = "A" ou key = "C" e, por isso o dataframe resultante tem NaN nessas entradas. key = "A". Como df2 tem 2 linhas para key = "D" a linha aparece duplicada para essa key em df1.

RIGHT JOIN

A junção RIGH OUTER JOIN recupera todos as campos à direita, existindo ou não uma linha correspondente à esquerda. O parâmetro how="right" é o equivalente no pandas.

–– sql: recupera todos os registros em df2
SELECT * FROM df1 RIGHT OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="right" equivale a RIGHT OUTER JOIN
pd.merge(df1, df2, on="key", how="right")
key value_x value_y
0 B 12 21
1 D 14 22
2 D 14 23
3 E NaN 24

FULL JOIN

A junção FULL OUTER JOIN recupera todos as campos à direita ou à esquerda, representando como NaN os valores ausentes em uma ou outra. Todos as linhas das duas tabelas são retornadas com junção onde a campo key existe em ambas. O parâmetro how="outer" é o equivalente no pandas. Observe que nem todos os gerenciadores de bancos de dados permitem essa operação.

–– sql: retorna todos os registros em ambas as tabelas
SELECT * FROM df1 FULL OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="outer" é o equivalente em dataframes
pd.merge(df1, df2, on="key", how="outer")
key value_x value_y
0 A 11 NaN
1 B 12 21
2 C 13 NaN
3 D 14 22
4 D 14 23
5 E NaN 24

UNION

Para os exemplos seguintes definimos mais 2 dataframes:

df3 = pd.DataFrame({"cidade": ["Rio de Janeiro", "São Paulo", "Belo Horizonte"], "nota": [1, 2, 3]})
df4 = pd.DataFrame({"cidade": ["Rio de Janeiro", "Curitiba", "Brasília"], "nota": [1, 4, 5]})

No SQL a clásula UNION ALL é usada para juntar as linhas retornadas em dois (ou mais) instruções de SELECT. Linhas duplicadas são mantidas. O mesmo efeito pode ser conseguido no pandas usando-se o método concat().

–– sql: UNION ALL
SELECT city, rank FROM df3 UNION ALL SELECT cidade, nota FROM df4;
# pandas: concat
pd.concat([df3, df4])
cidade nota
0 Rio de Janeiro 1
1 São Paulo 2
2 Belo Horizonte 3
0 Rio de Janeiro 1
1 Curitiba 14
2 Brasília 5

No SQL a cláusula UNION tem o mesmo efeito que UNION ALL mas remove as linhas duplicadas. No pandas isso pode ser conseguido se fazendo a conactenação concat() seguida de drop_duplicates().

–– SQL UNION
SELECT city, rank FROM df1 UNION SELECT city, rank FROM df2;
–– o registro duplicado no Rio de Janeiro fica excluído
# pandas: concat() seguido de drop_duplicates()
pd.concat([df1, df2]).drop_duplicates()
cidade nota
0 Rio de Janeiro 1
1 São Paulo 2
2 Belo Horizonte 3
1 Curitiba 14
2 Brasília 5

Outras funções analíticas e de agregamento

Para os próximos exemplos vamos retornar ao nosso dataframe dfGorjeta: para listar as 5 gorjetas mais altas, no MySQL (a sintaxe varia de um para outro gerenciador).

–– MySQL: retorna todos os campos em ordem decrescente, 5 linhas
SELECT * FROM dfGorjeta ORDER BY gorjeta DESC LIMIT 10 OFFSET 5;
# pandas: seleciona 15 maiores e exibe as 10 de menor valor
dfGorjeta.nlargest(15, columns="gorjeta").tail(10)
valor_conta gorjeta sexo fumante dia hora pessoas
183 23.17 6.50 homem sim Dom jantar 4
214 28.17 6.50 mulher sim sab jantar 3
47 32.40 6.00 homem não Dom jantar 4
239 29.03 5.92 homem não sab jantar 3
88 24.71 5.85 homem não Thur almoço 2
181 23.33 5.65 homem sim Dom jantar 2
44 30.40 5.60 homem não Dom jantar 4
52 34.81 5.20 mulher não Dom jantar 4
85 34.83 5.17 mulher não Thur almoço 4
211 25.89 5.16 homem sim sab jantar 4

UPDATE

Há muitas formas de alterar um valor em um campo de um dataframe. Por exemplo, abaixo realizamos uma alteração em todos os valores de gorjeta sempre que gorjeta < 2.

–– sql: em todas as linhas duplique a gorjeta se gorjeta for menor que 1.1
UPDATE dfGorjeta SET gorjeta = gorjeta*2 WHERE gorjeta < 1.1;
# pandas: o mesmo resultado pode ser obtido da aseguinte forma
# dfGorjeta.loc[dfGorjeta["gorjeta"] < 1.1, "gorjeta"] *= 2

Para explicar com mais detalhes o funcionamento deste código, armazenamos abaixo a lista dos índices das linhas de gorjetas mais baixas e exibimos essas linhas. Em seguida multiplicamos apenas as gorjetas dessas linhas por 2 e examinamos o resultado:

indices = dfGorjeta[dfGorjeta["gorjeta"] < 1.1].index
print("Índices de gorjetas < 1.1:", indices)
display("Lista de gorjetas < 1.1", dfGorjeta.iloc[indices])
# multiplica essas gorjetas por 2
dfGorjeta.loc[dfGorjeta["gorjeta"] < 1.1, "gorjeta"] *= 2
# lista as mesmas linhas após a operação
display("Gorjetas após a operação:", dfGorjeta.iloc[indices])
Índices de gorjetas < 1.1: Int64
Index([0, 67, 92, 111, 236], dtype=’int64′)
‘Lista de gorjetas < 1.1’

valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 1.01 mulher não dom jantar 2
67 3.07 1.00 mulher sim sab jantar 1
92 5.75 1.00 mulher sim sex jantar 2
111 7.25 1.00 mulher não sab jantar 1
236 12.60 1.00 homem sim sab jantar 2

‘Gorjetas após a operação:’

valor_conta gorjeta sexo fumante dia hora pessoas
0 16.99 2.02 mulher não dom jantar 2
67 3.07 2.00 mulher sim sab jantar 1
92 5.75 2.00 mulher sim sex jantar 2
111 7.25 2.00 mulher não sab jantar 1
236 12.60 2.00 homem sim sab jantar 2
–– sql: alterar um campo de uma linha específica (supondo a existência de um campo id)
UPDATE dfGorjeta SET sexo = 'NI' WHERE id = 239
# para alterar o campo sexo para 'NI' (não informado)
dfGorjeta.loc[239, 'sexo'] ='NI'

DELETE

Existem muitas formas de se excluir linhas de um dataframe mas é comum a prática de selecionar as linhas que devem ser mantidas e copiar para um novo dataframe.

–– sql: linhas são apagadas sob um certo critério
DELETE FROM dfGorjeta WHERE gorjeta > 9;
# pandas: como novo dataframe tem o mesmo nome do original, o antigo é sobrescrito e perdido
dfTop = dfGorjeta.loc[dfGorjeta["gorjeta"] > 9]
dfTop
valor_conta gorjeta sexo fumante dia hora pessoas
170 50.81 10.0 homem sim sab jantar 3

Também é possível apagar linhas usando seu índice:

# apagar linha com index = 4, inplace para substituir o dataframe
dfGorjeta.drop(index=4, inplace=True)
# apagar linhas com index = 0 até 3
dfGorjeta.drop(index=[0, 1, 2, 3], inplace=True)
dfGorjeta.head()
valor_conta gorjeta sexo fumante dia hora pessoas
5 25.29 4.71 homem não dom jantar 4
6 8.77 12.00 homem não dom jantar 2
7 26.88 3.12 homem não dom jantar 4
8 15.04 1.96 homem não dom jantar 2
9 14.78 3.23 homem não dom jantar 2
🔺Início do artigo

Bibliografia

  • McKinney, Wes: Python for Data Analysis, Data Wrangling with Pandas, NumPy,and IPython
    O’Reilly Media, 2018.
  • Pandas: página oficial, acessada em janeiro de 2021.

Biblioteca FactorAnalyser



FactorAnalyzer é um módulo Python para realizar análise fatorial exploratórias (AFE) e confirmatória (AFC). Na análise exploratória se pode usar várias técnicas de estimativa. É possível executar AFE usando (1) solução residual mínima (MINRES), (2) uma solução de máxima verossimilhança ou (3) uma solução de fator principal. A análise confirmatória só pode ser executada usando uma solução de máxima verossimilhança.

As classes da biblioteca são compatíveis com scikit-learn. Partes do código são portadas da biblioteca psych do R, e a classe AFC foi baseada na biblioteca sem. A classe factor_analyzer.factor_analyzer.FactorAnalyzer se utiliza de sklearn.base.BaseEstimator e sklearn.base.TransformerMixin.

Na AFE as matrizes de carga fatorial são geralmente submetidas a rotações após o modelo de análise fatorial ser estimado, buscando-se produzir uma estrutura mais simples e de fácil interpretação na identificação das variáveis que estão carregando em um determinado fator.

Os dois tipos mais comuns de rotações são:

  • Rotação ortogonal varimax, que gira a matriz de cargas fatoriais de forma a maximizar a soma da variância das cargas quadradas, preservando a ortogonalidade da matriz de carga.
  • A rotação oblíqua promax, baseada na rotação varimax, mas buscando maior correlaçãp dos fatores.

Essa biblioteca inclui o módulo analisador de fatores com a classe FactorAnalyzer. Essa classe inclui os métodos fit() e transform() para realizar a análise e pontuar novos dados usando o modelo de fatores ajustados. Rotações opcionais podem ser realizadas sobre a matriz de cargas fatoriais com a classe Rotator.

Os seguintes métodos de rotação estão disponíveis tanto para FactorAnalyzer quanto para Rotator:

  • varimax (rotação ortogonal),
  • promax (oblíqua),
  • oblimin (oblíqua),
  • oblimax (ortogonal),
  • quartimin (oblíqua),
  • quartimax (ortogonal),
  • equamax (ortogonal),
  • geomin_obl (oblíqua),
  • geomin_ort (ortogonal).

A biblioteca também inclui o módulo confirmatory_factor_analyzer com a classe ConfirmatoryFactorAnalyzer. A classe contém os métodos fit() e transform() que realizam análises fatoriais confirmatórias e pontuam novos dados usando o modelo ajustado. A execução do CFA requer que os usuários especifiquem previamente o modelo com as relações de carga fatorial esperadas, o que pode ser feito usando a classe ModelSpecificationParser.

# A biblioteca pode ser instalada usando pip:
$ pip install factor_analyzer

# ou conda:
$ conda install -c ets factor_analyzer

Ele depende de Python 3.4 (ou superior), numpy, pandas, scipy, scikit-learn.

A classe factor_analyzer possui um método para ajustar um modelo de análise e fazer a análise fatorial usando solução residual mínima (MINRES), solução de máxima verossimilhança ou (3) solução de fator principal. Retorna a matriz de cargas fatoriais. Por default não realiza nenhuma rotação. Opcionalmente pode realizar uma rotação usando os métodos citados abaixo.

Análise Fatorial Exploratória

Módulo factor_analyzer.factor_analyzer

class
factor_analyzer.factor_analyzer.FactorAnalyzer(n_factors=3, rotation='promax', method='minres',
                                               use_smc=True, is_corr_matrix=False, bounds=(0.005, 1),
                                               impute='median', rotation_kwargs=None)
Parâmetros:
n_factors
(int, opcional) – Número de fatores a ajustar. Default: 3.
rotation
(str, opcional) – Default: None.
Define o tipo de rotação a realizar após o ajuste do modelo. Nenhuma rotação será realizada se rotation = None e nenhuma normalização de Kaiser será aplicada. Os seguintes métodos de rotação estão disponíveis:

  • varimax (rotação ortogonal),
  • promax (oblíqua),
  • oblimin (oblíqua),
  • oblimax (ortogonal),
  • quartimin (oblíqua),
  • quartimax (ortogonal),
  • equamax (ortogonal),

sendo rotation = ‘promax’ o default.

method
({‘minres’, ‘ml’, ‘principal’}, opcional) – Método de ajuste a ser usado: solução residual mínima (MINRES), uma solução de máxima verossimilhança ou de fator principal. Default: minres.
use_smc
(bool, opcional) – Uso de correlação multipla quadrada (SMC, square multiple correlation) como ponto de partida para a análise. Default: True.
bounds
(tuple, opcional) – Limites inferior e superior das variáveis para a otimização “L-BFGS-B”. Default: (0.005, 1).
impute
({‘drop’, ‘mean’, ‘median’}, opcional) – Se existem valores faltantes nos dados usar apagamento na lista inteira (‘drop’) ou substituir valores usando a mediana (‘median’) ou a média (‘mean’) da coluna.
use_corr_matrix
(bool, opcional) – Ajuste para Verdadeiro se os dados de entrada já são a matriz de correlação. Default: False.
rotation_kwargs
(opcional) – argumentos adicionais passados para o método de rotação.
Propriedades:
loadings
A matriz de cargas fatoriais. Default: None se o método fit() não foi chamado.
Tipo: numpy array
corr
A matriz original de correlação. Default: None se o método fit() não foi chamado.
Tipo: numpy array
rotation_matrix
A matriz de rotação, se uma rotação não foi executada.
Tipo: numpy array
structure
A estrutura da matriz de cargas fatoriais. (Só existe se rotação for promax).
Tipo: numpy array ou None
psi
A matriz de fatores de correlação. (Só existe se rotação for oblíqua).
Tipo: numpy array ou None
Métodos:
fit(X, y=None)
Faz a análise fatorial sobre os dados de X usando ‘minres’, ‘ml’, ou ‘principal solutions’. Default: SMC (correlação múltipla quadrada)
Parâmetros:
     X (variável tipo array) – Dados originais a serem analisados.
     y (não é utilizado).
get_communalities()
Calcula as comunalidades, dada uma matriz de cargas fatoriais.
Retorna: communalities (numpy array), as comunalidades.
get_eigenvalues()
Calcula os autovalores, dada uma matriz de correlações fatoriais.
Retorna: original_eigen_values (numpy array), os autovalores originais.
    common_factor_eigen_values(numpy array), os autovalores dos fatores comuns.
get_factor_variance()
Calcula informações de variância, inclusive a variância, variância proporcional e cumulativa para cada fator.
Retorna: variance (numpy array) – A variância dos fatores.
    proportional_variance (numpy array) – A variância proporcional dos fatores.
    cumulative_variances (numpy array) – A variância cumulativa dos fatores.
get_uniquenesses()
Calcula a unicidade, dada uma matriz de cargas fatoriais.
Retorna: uniquenesses (numpy array) – A unicidade da matriz de cargas fatoriais.
transform(X)
Retorna a pontuação de fatores para um novo conjunto de dados.
Parâmetros: X (variável de array, shape (n_amostras, n_características)) – Os dados a pontuar usando o modelo de fatores ajustado.
Retorna: X_new – As variáveis latentes de X. (um numpy array, shape (n_amostras, n_componentes))

Testes de Bartlett e KMO

factor_analyzer.factor_analyzer.calculate_bartlett_sphericity(x)

Testa a hipótese de que a matriz de correlação é igual à matriz identidade (o que significa que não há correlação entre as variáveis).

Parâmetro
X (variável de array) – Dados sobre os quais calcular a esfericidade.
Retorna:
statistic (float) – O valor de chi-quadrado, \chi^2.
p_value (float) – O valor de p-value associado ao teste.
factor_analyzer.factor_analyzer.calculate_kmo(x)

Calcula o critério de Kaiser-Meyer-Olkin para cada fator individual e para o conjunto fatores. Essa estatística representa o grau com que cada variável observada pode ser predita, sem erro, pelas demais variáveis. Na maior parte dos casos KMO < 0.6 é considerado inadequado.

Parâmetros:
x (variável de array) – Dados sobre os quais calcular coeficientes KMOs.
Retorna:
kmo_per_variable (array numpy) – O KMO para cada item.
kmo_total (float) – A pontuação KMO geral.

Análise Fatorial Confirmatória

Módulo factor_analyzer.confirmatory_factor_analyzer

class
factor_analyzer.confirmatory_factor_analyzer.ConfirmatoryFactorAnalyzer(specification=None,
                                n_obs=None, is_cov_matrix=False, bounds=None, max_iter=200,
                                tol=None, impute='median', disp=True)

A classe ConfirmatoryFactorAnalyzer ajusta um modelo de análise fatorial confirmatória usando o modelo de máxima verosimelhança (maximum likelihood).

Parâmetros:
specification
(ModelSpecification object ou None, opcional) – Um modelo de especificação ModelSpecification ou None. Se None for fornecido o ModelSpecification será gerado supondo que o número de fatores n_factors é o mesmo que o número de variáveis, n_variables, e que todas as variáveis carregam em todos os fatores. Note que isso pode significa que o modelo não foi identificado e a otimização poderá falhar. Default: None.
n_obs
(int ou None, opcional) – Número de observações no conjunto original de dados. Se não for passado e is_cov_matrix=True um erro será lançado. Default: None.
is_cov_matrix
(bool, opcional) – Informa se a matriz de entrada X é a matriz de covariância. Se False o conjunto completo de dados é considerado. Default: False.
bounds
(lista de tuplas ou None, opcional) – Uma lista de limites mínimos e máximos para cada elemento do array de entrada. Deve ser igual a x0 que é o array de entrada do modelo de especificação.

O comprimento é: ((n_factors * n_variables) + n_variables + n_factors + (((n_factors * n_factors) – n_factors) // 2)

Se bounds=None nenhum limite será tomado. Default: None.

max_iter
(int, opcional) – Número máximo de iterações na rotina de otimização. Default: 200.
tol
(float ou None, opcional) – Tolerância para a convergência. Default: None.
disp
(bool, opcional) – Se a mensagem da otimização do scipy fmin é enviado para o standard output. Default: True.
Exceção levantada
ValueError – Se is_cov_matrix = True e n_obs não é fornecido.
Propriedades:
model
Objeto modelo de especificação.
Tipo: ModelSpecification.
loadings_
A Matriz da Cargas fatoriais.
Tipo: numpy array
error_vars_
A matriz de variância de erros (Tipo: numpy array).
factor_varcovs_
A Matriz de covariância. (Tipo: numpy array).
log_likelihood_
O log de verossimilhança da rotina de otimização. (Tipo: float).
aic_
O critério de informação de Akaike. (Tipo: float).
bic_
O critério de informação Bayesiano. (Tipo: float).
Métodos:
fit(X, y=None)
realiza a análise fatorial confirmatória sobre os dados de X usando máxima verossimilhança.
Parâmetros:
X (variável tipo array) – Dados originais a serem analisados. Se X for a matriz de covariância é necessário marcar is_cov_matrix = True.
y (não é utilizado)
Exceção levantada:
ValueError – Se a especificação não é um objeto ModelSpecification nem None.
AssertionError – Se is_cov_matrix = True e a matriz fornecida não é quadrada.
AssertionError – If len(bounds) != len(x0)
get_model_implied_cov()
Retorna a matriz de covariância implícita no modelo, se o modelo foi estimado. (numpy array)
get_standard_errors()
Lê os erros padrão da matriz de covariância implícita e das médias implícitas.
Retorna:
loadings_se (numpy array) – Os erros padrões para os cargas fatoriais.
error_vars_se (numpy array) – Os erros padrões para as variâncias de erros.
transform(X)
Lê as pontuações para os fatores do novo conjunto de dados.
Parâmetros: X (tipo array, shape (n_amostras, n_características)) – Os dados a serem pontuados usando o modelo fatorial ajustado.
Retorna: pontuações – As variáveis latentes de X (um numpy array, shape (n_amostras, n_componentes))
class
factor_analyzer.confirmatory_factor_analyzer.ModelSpecification(loadings, n_factors, n_variables,
                                                                factor_names=None, variable_names=None)

Uma classe para encapsular a especificação do modelo para a análise confirmatória. Esta classe contém várias propriedades de especificação que são usadas no procedimento AFC.

Parâmetros:
loadings
(tipo array) – Especificação de cargas fatoriais.
error_vars (array-like) – Especificação de variância do erro
factor_covs (tipo array) – Especificação da covariância do fator.
factor_names (lista de str ou None) – uma lista de nomes de fatores, se disponível. Default: None.
variable_names (lista de str ou None) – Uma lista de nomes de variáveis, se disponível. Default: None.
Propriedades:
loadings
Especificação das cargas fatoriais. (Tipo: numpy array)
error_vars
Especificação da variância de erros. (Tipo: numpy array)
factor_covs
Especificação do fator de covariância. (Tipo: numpy array)
n_factors
Número de fatores. (Tipo: int).
n_variables
Número de variáveis. (Tipo: int).
n_lower_diag
Número de elementos no array factor_covs, igual à diagonal inferior da matriz de covariância fatorial. (Tipo: int).
loadings_free
Índices dos parâmetros de cargas fatoriais “livres”. (Tipo: numpy array)
error_vars_free
Índices dos parâmetros de variância de erros “livres”. (Tipo: numpy array)
factor_covs_free
Índices dos parâmetros de covariância de fator “livre”. (Tipo: numpy array)
factor_names
Lista dos nomes dos fatores, se disponível. (Tipo: lista de str ou None).
variable_names
Lista dos nomes das variáveis, se disponível. (Tipo: lista de str ou None).
error_vars
error_vars_free
factor_covs
factor_covs_free
factor_names
Métodos:
get_model_specification_as_dict()
Retorna model_specification – O modelo de especificações, chaves e valores, como um dicionario (dict).
copy()
Retorna uma “deep copy” do objeto.
loadings
loadings_free
n_factors
n_lower_diag
n_dtiables
dtiable_names

Analisador de especificação de modelo

class factor_analyzer.confirmatory_factor_analyzer.ModelSpecificationParser

Uma classe para gerar a especificação do modelo para Análise Fatorial Confirmatória. Inclui dois métodos estáticos para gerar o objeto ModelSpecification a partir de um dicionário ou de um array numpy.

static parse_model_specification_from_array(X, specification=None)

Gera a especificação do modelo a partir de um array. As colunas devem corresponder aos fatores e as linhas às variáveis. Se este método for usado para criar a ModelSpecification nenhum nome de fator ou nome de variável serão adicionadoa como propriedades desse objeto.

Parâmetros:
X (tipo array) – O conjunto de dados será usado para a AFC.
specification (tipo array or None) – Array com os detalhes das cargas.
Se specification = None a matriz será criada supondo-se que todas as variáveis carregam em todos os fatores. Default: None.
Retorna:
Objeto modelo de especificação, ModelSpecification.
Exceção levantada:
ValueError se especificação não está no formato esperado.
static parse_model_specification_from_dict(X, specification=None)

Gere a especificação do modelo a partir de um dicionário. As chaves no dicionário devem ser os nomes dos fatores e os valores devem ser os nomes das características (features). Se esse método for usado para criar uma ModelSpecification os nomes dos fatores e das variáveis serão adicionados como propriedades a esse objeto.

Parâmetros:
X (tipo array) – O conjunto de dados será usado para a AFC.
specification (tipo array or None) – Dicionário com os detalhes das cargas. Se None a matriz será criada supondo-se que todas as variáveis carregam em todos os fatores. Defaults = None.
Retorna: Objeto modelo de especificação, ModelSpecification.
Exceção levantada: ValueError se especificação não está no formato esperado.

Módulo factor_analyzer.rotator

Classe para executar diversas rotações nas matrizes de cargas fatoriais. Os métodos de rotação listados abaixo estão disponíveis tanto para FactorAnalyzer quanto para Rotator.

class
factor_analyzer.rotator.Rotator(method='varimax', normalize=True, power=4,
                                kappa=0, gamma=0, delta=0.01, max_iter=500, tol=1e-05)
Parâmetros:
method
(str, opcional). Métodos de rotação disponíveis: Default: ‘varimax’.

  • varimax (rotação ortogonal),
  • promax (oblíqua),
  • oblimin (oblíqua),
  • oblimax (ortogonal),
  • quartimin (oblíqua),
  • quartimax (ortogonal),
  • equamax (ortogonal),
  • geomin_obl (oblíqua),
  • geomin_ort (ortogonal).
normalize
(bool ou None, opcional) – Informa se uma normalização de Kaiser e de-normalização devem ser efetuadas antes e após a rotação. Usada para as rotações varimax e promax. Se None o default para promax é False, default para varimax é True. Default: None.
power
(int, opcional) – Potência das gargas de promax (menos 1). Geralmente na faixa entre 2 e 4. Default: 4.
kappa
(int, opcional) – Valor kappa para o objetivo equamax. Ignorado se método não for ‘equamax’. Default: 0.
gamma
(int, opcional) – Nível gamma para o objetivo ‘oblimin’. Ignorado se método não for ‘oblimin’. Default: 0.
delta
(float, opcional) – Nível delta level para o objetivo ‘geomin’. Ignorado se método não for ‘geomin_*’. Default: 0.01.
max_iter
(int, opcional) – Número máximo de iterações. Usado para método ‘varimax’ e rotações oblíquas. Default: 1000.
tol
(float, opcional) – Limite (threshold) de convergência. Usado para método ‘varimax’ e rotações oblíquas. Default: 1e-5.
Propriedades:
loadings_
A matriz de carregamentos. (Tipo: numpy array, shape (n_características, n_fatores)

rotation_

Matriz de rotação. (Tipo: numpy array, shape (n_fatores, n_fatores))

psi_

Matriz de correlações fatoriais. Existe apenas se a rotação é oblíqua. (Tipo: numpy array ou None)

Métodos:
fit(X, y=None)
Computa o fator de rotação model_specification – O modelo de especificações, chaves e valores, como um dicionario (dict).

Parâmetros
X (tipo array) – A matriz (n_características, n_fatores) de cargas fatoriais
y (não utilizado)
Retorna
self
fit_transform(X, y=None)
Computa o fator de rotação e retorna a nova matriz de cargas fatoriais.

Parâmetros
X (tipo array) – A matriz (n_características, n_fatores) de cargas fatoriais
y (não utilizado)
Retorna
loadings_, a matriz (n_características, n_fatores) de cargas fatoriais (numpy array)
Erro levantado
ValueError – se o método não está na lista de métodos aceitáveis.

Bibliografia

Jupyter Notebook


O que é o Jupyter Notebook?

Jupyter Notebook é uma ferramenta poderosa para o desenvolvimento projetos de ciência de dados de forma interativa que é executada de dentro de seu browser. Além da execução interativa de código ele pode ser usado para a preparação de apresentações dinâmicas onde texto pode ser bem formatado contendo imagens, gráficos interativos e fórmulas matemáticas. O conteúdo das linhas de código e seus outputs (as respostas produzidas pelo código) ficam bem delimitadas.

Ele faz parte do Projeto Jupyter de código aberto e free software. É possível baixar e instalar gratuitamente o Jupyter Notebooks separadamente ou como parte do kit de ferramentas de ciência de dados Anaconda. Com ele se pode usar vários ambientes de execução (kernels ou núcleos) com linguagens de programação como o Python, R, Julia, C++, Scheme, Haskell e Ruby.

O Jupyter Notebook está se tornando uma interface muito popular para a computação na nuvem e muitos dos provedores desse tipo de serviço o adotaram como interface front-end para seus usuários. Entre eles estão o SageMaker Notebooks da Amazon, o Colaboratory do Google e o Azure Notebook da Microsoft. O Colaboratory (também chamado de Colab) é um ambiente gratuito que armazena e roda notebooks no Google Drive.

Os arquivos no Notebook são armazenados no formato JSON com a extensão *.ipynb. Eles podem ser lidos pelo aplicativo instalado (que usa o browser para sua exibição), e exportado para várias formatos, entre eles: html, markdown, pdf, latex e slides Reveal.js. O arquivo consiste de várias células que são exibidas como uma página usual de html. Algumas células são de código e seus resultados, outras de texto, imagens ou outras mídias. Alternativamente eles podem ser lidos e exibidos (de forma não interativa) pelo nbviewer (na web).

Instalação

A forma mais simples de instalação do Jupyter Notebooks consiste em instalar o Anaconda, a distribuição Python mais usada para ciência de dados. Esta instalação inclui as princiais bibliotecas e ferramentas mais populares, como NumPy, Pandas e Matplotlib.

Para obter o Anaconda:

  • Baixe a versão mais recente do Anaconda aqui. Existem instaladores gráficos para Linux, Windows e MacOS.
  • Instale seguindo as instruções na página de download ou contidas no arquivo instalador executável.
  • ou … usuários que conhecem Python e que preferem gerenciar seus pacotes manualmente, pode apenas usar:
    pip3 install jupyter.

Executando e Criando um Notebook

No Windows na MacOS ou Linux um atalho para Jupyter Notebook é criado no menu do sistema. Caso nenhum atalho tenha sido criado você pode criar um ou iniciar usando o console. No Linux se digita jupyter-notebook no console. Uma janela com a seguinte aparência é aberta no browser padrão:

A primeira página exibe o painel de controle do Jupyter (dashboard). Ele dá acesso aos arquivos e subpastas que estão na pasta de inicialização do Jupyter. Esta pasta, no entanto, pode ser alterada. O painel de controle tem uma interface amigável por onde você pode controlar suas pastas e notebooks. Toda a descrição que usamos aqui se refere à aparência default dos notebooks. Essa aparência pode ser alterada modificando-se o arquivo css em /home/nome-usuario/.jupyter/custom/custom.css (no Linux).

O Jupyter Notebook fica aberto no navegador usando uma URL especial, do tipo “http://localhost:8888/tree”. Isso indica que o servidor dessa página esta em sua máquina local.

Você pode criar uma pasta nova para seus projetos clicando em New Folder. Depois pode renomear sua pasta selecionado e clicando em Rename. Clique na pasta para abrí-la e depois em New: Python 3 (notebook). Um notebook vazio e sem nome é criado. Clique nele e em File | Rename e dê um nome apropriado para o seu notebook. Vários notebooks podem ser abertos ao mesmo tempo.

Um notebook aberto apresenta a seguinte interface:

Os menus superiores dão acesso as funcionalidades de gravar arquivo, importar e exportar para diferentes formatos, etc. O item File | Save and Checkpoint (Ctrl-S) permite guardar o estado as sessão no momento de forma a poder ser recuperada mais tarde por meio de File | Revert to Checkpoint. Para fechar uma sessão não basta fechar a aba do navegador pois isso deixaria ativa a tarefa na memória do computador. Para isso você pode usar File | Close and Halt ou Kernel | Shutdown. Também se pode clicar em LOGOUT no canto superior direito.

Ainda na barra de menus você pode clicar em Help | User interface tour para ver alguns pontos relevantes no notebook. Help | Keyboard Shortcuts exibe os atalhos de teclado predefinidos, observando que eles podem ser modificados pelo usuário. O menu de ajuda também contém links diretos para as documentações do Python, IPython, NumPy, SciPy, Matplotlib, SymPy, pandas e markdown. O ícone de teclado abre uma ajuda com os atalhos mais usados.

Notebooks são iniciados com uma célula vazia. Cada célula possui dois modos de uso: modo de edição e modo de comando. Clicando dentro da célula ela fica com borda verde e entra no modo de edição onde se pode inserir texto e códigos. Use a caixa de seleção na caixa de ferramentas do notebook para selecionar entre ‘code’, ‘markdown’ ou ‘Raw NBConvert’. (A opção ‘heading’ está em desuso e é desnecessária). Se ‘code’ está selecionado você pode digitar um comando como, por ex.,

print('Olá galera!')
Olá galera!

Comandos dentro de uma célula de código são executados todos de uma vez. Para isso pressione:
shift-enter: o código é executado e uma nova célula é aberta no mode de edição, ou
ctrl-enter: o código é executado e a mesma célula fica selecionada no modo de comando.

Em ambos os casos o output, se existir, é mostrado abaixo da célula. O botão ▶ Run na barra de ferramentas tem o mesmo efeito que shift-enter.

Os modos de edição e de comando são alternados com o uso de ESC (passa de comando para edição) e enter (de edição para comando).

Quando no modo de comando (a célula fica com a borda azul) os seguintes atalhos ficam habilitados:

Comando Ação
ESC/enter Alterna entre modos de edição / comando
Barra de espaço Rolar tela para baixo
Shift-Barra de espaço Rolar tela para cima
A Inserir célula acima
B Inserir célula abaixo
D, D Apagar células selecionadas
Z Desfazer apagamento
Shift-L Exibir/ocultar numeração das linhas de código
Shift-Tab Exibir docstring de objeto
Y Alterar célula para código
M Alterar célula para markdown
R Alterar célula para raw
H Exibir Help | keyboard shortcuts
Teclas 🔼 ou 🔽 Rolar para cima e para baixo nas células
Shift 🔼 ou 🔽 Selecionar várias células de uma vez.
Shift M Mesclar várias células selecionadas em uma única.
Ctrl Shift – Dividir célula ativa na posição do cursor (no modo de edição).
Shift clique na margem esquerda da célula Selecionar várias células de uma vez.

Um lista completa desses atalhos pode ser vista no menu Help | keyboard shortcuts.

Células no formato de texto

Células podem ser marcadas como texto puro, sem qualquer formatação, no modo Raw NBConvert. Esse texto é exibido como está e não passará por renderização de nenhuma forma nem na exibição dentro do notebook nem na exportação para outros formatos, como o pdf.

Markdown

Markdown é uma linguagem de marcação limitada mas bastante simples de se aprender e usar. Ela tem sintaxe diferente de html mas guarda uma correspondência com suas tags. Você pode encontrar uma descrição mais completa de Markdown na página Marcação de Texto com Markdown.

Além da marcação markdown se pode usar um conjunto restrito de tags html nas caixas de texto. Por exemplo, o código seguinte:

 <div style = "border:1px solid black; padding:10px; color:white; background-color:red;" >
parei aqui ↷ 
</div>

produzirá a barra vermelha abaixo, útil para marcar um ponto específico dentro de um notebook.

parei aqui ↷

O Jupyter Notebook pode renderizar Latex (para isso usa a biblioteca javascript Mathjax) em suas células de Markdown.


A identidade de Euler é \(e^{i \pi} + 1 = 0\).
A definição da derivada de primeira ordem é:
$$
f'(x) = \lim\limits_{h \rightarrow 0} \frac{f(x+h) – f(x)}{h}
$$

Células de código

Células de código contém as instruções a serem executadas pelo kernel. À esquerda de uma célula de código aparece o sinal In[n] que significa Input [n], onde n é a ordem de execução das linhas. Uma célula em exibição é marcada pelo sinal In[*], o que é importante observar quando se tem uma execução mais demorada de código. Em geral o notebook continua responsivo mas novas instruções devem aguardar o término das primeiras para serem executados. O resultado da computação da célula, quando existir, aparece abaixo com o sinal Out[n].

O código em um célula ativa (com bordas azuis) é executado com ctrl-enter ou shift-enter (alternativamente com botão ▶ Run na barra de ferramentas). A barra de menu contém as opções Cell | Run All, Cell | Run All Above Cell | Run All Below.

É importante notar que as células são executadas no kernel e seu estado persiste ao longo da execução. O estado do kernel se refere ao notebook como um todo e não às células individuais. Se você importar bibliotecas ou declarar variáveis ​​em uma célula elas estarão disponíveis em toda a seção e não apenas nas células abaixo da declaração. A ordem em que inputs e outputs aparecem pode ser enganosa: se você declarar uma variável em uma célula, digamos g = 5 e voltar para uma célula anterior a variável g ainda terá o mesmo valor. O que importa é a ordem de execução, marcada por In[n]. Por isso é preciso ter cuidado ao fazer alterações nas células. Idealmente se deve manter a ordem sequencial e, ao fazer alterações importantes, executar todo o notebook novamente.

Existem várias opções relativas à execução no menu Kernel:

Kernel | Interrupt Interromper o kernel,
Kernel | Restart Reinicializar o kernel,
Kernel | Restart & clear outputs Reinicializar kernel e limpar outputs,
Kernel | Restart & Run All Reinicializar kernel e executar todas as células.

A opção de reinicializar o kernel limpando as saídas e executar novamente todas as células pode ser útil quando muitas células foram alteradas. No entanto, dependendo das operações a serem executadas, ela pode ser uma operação demorada.

É possível escolher e rodar e Jupyter Notebook com diferentes opções de kernel, com diferentes versões de Python e muitas outras linguagens, incluindo Java, C, R e Julia.

O Notebook é salvo ao se pressionar Ctrl S. Isso renova o arquivo de ponto de verificação que é criado junto com o Notebook. Esse arquivo, que também tem extensão .ipynb fica localizado em pasta oculta e pode ser usado na recuperação da sessão caso algum erro inesperado cause a perda do notebook atual. Por padrão o Jupyter salva automaticamente o bloco de notas a cada 120 segundos sem alterar o arquivo de bloco de notas principal.Para reverter para um ponto de verificação use o menu via File | Revert to Checkpoint.

Tendo escolhido um notebook com kernel python 3, podemos experimentar um resultado com a execução de código que fornece um output de texto e gráfico:

# importe as bibliotecas necessárias
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(1, 15, 0.1);
y = np.sin(x)/np.cos(x)
z = np.cos(x)/np.sin(x)
print("x em [1, 15] \ny=seno/cosseno; z=cosseno/seno")
plt.plot(x, y)
plt.plot(x, z)
x em [1, 15]
y=seno/cosseno; z=cosseno/seno

É importante observar que a última variável listada em uma célula é exibida no output sem necessidade de um comando print. Por ex., no código abaixo importamos uma tabela disponível na web no formato *.csv para a construção de um dataframe do pandas. O dataframe é exibido com uma formatação amigável no final da célula.

1  import pandas as pd
2  bfi ='https://vincentarelbundock.github.io/Rdatasets/csv/psych/bfi.csv'
3  df = pd.read_csv(bfi)
4  # head exibirá apenas as 5 primeiras linhas do dataframe
5  df.head()
Unnamed: 0 A1 A2 A3 A4 A5 C1 C2 C3 C4 N4 N5 O1 O2 O3 O4 O5 gender education age
0 61617 2.0 4.0 3.0 4.0 4.0 2.0 3.0 3.0 4.0 2.0 3.0 3.0 6 3.0 4.0 3.0 1 NaN 16
1 61618 2.0 4.0 5.0 2.0 5.0 5.0 4.0 4.0 3.0 5.0 5.0 4.0 2 4.0 3.0 3.0 2 NaN 18
2 61620 5.0 4.0 5.0 4.0 4.0 4.0 5.0 4.0 2.0 2.0 3.0 4.0 2 5.0 5.0 2.0 2 NaN 17
3 61621 4.0 4.0 6.0 5.0 5.0 4.0 4.0 3.0 5.0 4.0 1.0 3.0 3 4.0 3.0 5.0 2 NaN 17
4 61622 2.0 3.0 3.0 4.0 5.0 4.0 4.0 5.0 3.0 4.0 3.0 3.0 3 4.0 3.0 3.0 1 NaN 17

2800 rows × 29 columns

Ajuda

É sempre útil lembrar os mecanismos de se conseguir ajuda no python e no Jupyter. A função help do python é útil para se ver documentação sobre um tipo de variável ou uma bliblioteca importada. (Em ambos os casos a saída exibida está truncada.)

# ajuda sobre dictionaries
help(dict)
Help on class dict in module builtins:
class dict(object)
| dict() -> new empty dictionary
| dict(mapping) -> new dictionary initialized from a mapping object’s
| (key, value) pairs
| dict(iterable) -> new dictionary initialized as if via:
| d = {}
| for k, v in iterable:
| d[k] = v
import math
help(math)
Help on module math:
FUNCTIONS
acos(x, /)
Return the arc cosine (measured in radians) of x.
acosh(x, /)
Return the inverse hyperbolic cosine of x.
asin(x, /)
Return the arc sine (measured in radians) of x.

O comando help() usado sem argumento abre uma janela de busca interativa do tópico de ajuda.

Além disso escrever o nome de uma biblioteca, método ou variável precedido de ? provocará a exibição da docstring relativa ao item, o que é uma boa forma de conferir uso e sintaxe.

import pandas as pd
?pd
Type: module
String form: <module ‘pandas’ from ‘/home/guilherme/.anaconda3/lib/python3.7/site-packages/pandas/__init__.py’>
File: ~/.anaconda3/lib/python3.7/site-packages/pandas/__init__.py
Docstring:
pandas – a powerful data analysis and manipulation library for Python
=====================================================================
**pandas** is a Python package providing fast, flexible, and expressive data
structures [… texto truncado …]

Além disso é possível exibir a docstring de um objeto no Notebook apertando Shift-Tab após o nome do objeto dentro da célula.

Células e Linhas Mágicas em Jupyter

Uma sintaxe especial dos notebooks consiste em inserir comandos iniciados por %% para “mágica” na célula inteira e % para “mágica” na linha. Por exemplo %matplotlib inline faz com que gráficos do matplotlib sejam renderizados dentro do próprio notebook. Os sinais % e %%indicam respectivamente “mágica” na linha ou na célula. Digitar %lsmagic na célula e executá-la exibirá uma lista de todos os comandos mágicos de célula e de linha, informando ainda se automagic está ativado. Se automagic = ON os prefixos %% e % são desnecessários.

Alguns exemplos de “mágicas”:

Mágica Efeito
%run Roda um script externo como parte da célula.
Ex.: %run meu_codigo.py
executa meu_codigo.py como parte da célula.
%timeit Conta os loops, mede e exibe o tempo de execução da célula.
%prun Mostra o tempo de execução de uma função.
%%writefile Salva o conteúdo da célula para um arquivo.
Ex.: %%writefile meu_output.py
salva o código da célula no arquivo meu_output.py.
%%pycat Lê e exibe o conteúdo de um arquivo.
Ex.: %%pycat meu_output.py
lê o conteúdo gravada antes em meu_output.py.
%macro Define uma macro para execução posterior.
%store Salva a variável para uso em outro notebook.
%pwd Exibe a pasta default de trabalho.
%who Exibe varáveis ativas.
%matplotlib inline Exibe gráficos matplotlib inline.
%lsmagic Exibe lista de comandos “mágicos”.
%automagic Liga/desliga automagica (necessidade de %).
%%javascript Roda a célula como código JavaScript.
%%html Exibe código html renderizado.
%pdb Aciona o Python Debugger.
%<comando>? Mostra ajuda sobre a %<comando>
%%script python2
import sys
print 'Esse comando foi executado no Python %s' % sys.version
# output---> Esse comando foi executado no Python 2.7.18 (default, data / hora) 
%%script python3
import sys
print('Comando executado no Python: %s' % sys.version)
# output---> Esse comando foi executado no Python 3.8.5 (default, data / hora) 
%%bash
echo "O conteúdo de minha variável BASH: $BASH"
# output---> O conteúdo de minha variável BASH: /usr/bin/bash
# Definição de fatorial usando recursão	
def fatorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)
fatorial(40)      # resulta em 87178291200
# Claro que a mesma coisa seria obtida com a função
import math
print(math.factorial(14))    # 87178291200

Para medir o tempo de execução da célula ao rodar a função fatorial usamos %timeit

%timeit fatorial(14)

1.78 µs ± 16.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Executando comandos da SHELL

Usando o prefixo ! você pode enviar comandos diretamente para a shell de seu sistema operacional. Os comandos dependerão, claro, de seu sistema. No exemplo seguinte uso o bash do linux.

! ls *.ipynb     # vai listar todos os arquivos ipynb
! ls *.py        # lista meu_output.py gravado no exemplo anterior 

# mv é o comando bash para renomear arquivos
! mv meu_output.py novo_nome.py 
! ls *.py        # lista novo_nome.py

# Se você tem pip instalado pode gerenciar pacotes python de dentro do notebook:
!pip install --upgrade nome_do_pacote

Extensões do Jupyter Notebook


Extensões são recursos adicionais que estendem a funcionalidade dos Notebooks. Elas podem melhorar sua funcionalidade como uma IDE (um ambiente integrado de desenvolvimento), permitindo o acompanhamento de variáveis para fins de debugging, formatando o código automaticamente ou alterando sua aparência. Elas também permitem a inserção de widgets, que geralmente são objetos com representação gráfica no navegador, tais como caixas de texto, sliders e outras ferramentas interativas. Elas adicionam funcionalidades GUIs nos notebooks.

Uma extensão muito usada é a Nbextensions que pode ser instalada à partir do código seguinte, inserido no próprio notebook.

conda install -c conda-forge jupyter_contrib_nbextensions
conda install -c conda-forge jupyter_nbextensions_configurator 

A instalação de extensões pode variar de acordo com o seu ambiente de trabalho.

Jupyter Widgets

Widgets são objetos do python que possuem uma representação visual no navegador e permitem interação do usuário. Eles podem ser simples como uma caixa de texto ou um controle de arrastar, ou objetos sofisticados como os controles sobre uma tabela de dados ou sobre um mapa.

Slider é um widget útil para receber um valor do usuário por meio do mouse ou toque de tela. O objeto possui propriedades que podem ser alteradas e lidas, e métodos.
No exemplo abaixo um slider é criado, algumas de suas propriedades são ajustadas e depois é exibido.

import ipywidgets as ipw
# Inicializar um slider
slid = ipw.IntSlider()
slid.min = 0
slid.max = 50
slid.description = "Avalie:"
display(slid) # Exibe o slider ( figura a)

# O objeto pode ser inicializado com as propriedades desejadas
slid2 = ipw.IntSlider(
    value=0,
    min=0,
    max=10,
    step=2,
    description='Só pares:',
    disabled=False,
    continuous_update=True,
    orientation='horizontal',
    readout=True,
    readout_format='d'
)
display(slid2) # Exibe o slider ( figura b)

Button é um widget que pode armazenar interação do usuário em resposta binária (sim/não, por ex.).

ipw.Button (
    description='Clica aí',
    disabled=False,
    tooltip='clica...',
    button_style='success', # pode ser 'success', 'info', 'warning', 'danger' ou ''
    icon='check'
)
# Exibe o botão (figura c)

Text apresenta uma caixa de texto a ser preenchida pelo usuário:

ipw.Text (
    placeholder='Digite seu nome',
    description='Nome:',
)
# Exibe a caixa de texto ( figura d)

Progress Bar mostra uma barra de progresso:

ipw.IntProgress (
    min=0,
    max=10,
    value=7,
    description='Realizado %',
    bar_style='success', # pode ser 'success', 'info', 'warning', 'danger' ou ''
    orientation='horizontal'
)
# Exibe a barra de progresso ( figura e)

Para serem úteis as widgets devem interagir com o código. Como ex. vamos criar uma barra de progresso atribuindo o objeto a uma variável.

pb = ipw.IntProgress (
    min=0,
    max=10,
    value=7,
    description='Realizado%',
    bar_style='success', # pode ser 'success', 'info', 'warning', 'danger' ou ''
    orientation='horizontal'
)

print('valor inicial = ', pb.value) # value é uma propriedade de pb. Neste caso seu valor é 7

pb.value = 10                       # que pode ser alterada no código
display(pb)	                        # exibe uma barra de progesso cheia

No exemplo seguinte fazemos uma interação entre os valores da barra de progresso com o clique de dois botões. O código usa o evento on_click dos botões para acionar as funções que aumentam e diminuem o valor da barra.

def aumentaBarra(s):
    pb.value +=1

def diminuiBarra(s):
    pb.value -=1

botaoMais = ipw.Button(description='Aumenta barra')
botaoMais.on_click(aumentaBarra)

botaoMenos = ipw.Button(description='Diminue barra')
botaoMenos.on_click(diminuiBarra)
# display(botaoMenos, botaoMais, pb)
# -- isso exibiria os controles um sobre o outro

# Para exibir os controles em uma linha única com título
# usamos HBox, Label

from ipywidgets import HBox, Label
HBox([Label('Em uma linha única:'), botaoMenos, botaoMais, pb])         # exibe figura (f)


Existem widgets mais sofisticados para formatação dos controles, inclusive com o uso de html e formatação css. Apenas para mostrar um exemplo segue o código abaixo, que usa HBox para construir uma linha e VBox para construir uma coluna de controles.

from ipywidgets import HBox, VBox, Label	
linha1 = HTML('<h2 style="color: red;">Use controles para modificar <i>progress box</i> </h2>')
linha2 = HBox([Label('Botões de controle:'), botaoMenos, botaoMais])
linha3 = pb
VBox([linha1, linha2, linha3]) # exibe figura (g)


Um clique no primeiro botão faz a barra de progressa avançar, no segundo faz a barra regredir.

Observe importamos os controles HBox,VBox e Label de ipywidget e, portanto eles podem ser usados diretamente sem menção à biblioteca de origem. Poderíamos usar a notação ipw.HBox para usar a caixa horizontal, sem precisar da importação explícita.

Outras informações sobre formatação dos widgets em
Layout and Styling of Jupyter widgets
.

Outros Widgets Úteis

HTMLMath serve para exibir equações matemáticas:

equacao = ipw.HTMLMath(
    value = r"Widgets podem ser usados para exibir equações inline \(x^2\) e destacadas $$\frac{x^2-1}{x-1}= x+1$$",
    placeholder='HTML',
    description='MathML:',
)
display(equacao)
MathML: Widgets podem ser usados para exibir equações inline \(x^2\) e destacadas $$\frac{x^2-1}{x-1}= x+1$$

ipyleaflet é uma biblioteca para a criação de mapas interativos que permitem marcação, arraste e ampliação. Em muitos casos, para executar um biblioteca recém instalada, pode ser necessário reinicializar o kernel.

# A biblioteca pode ser instalada com condas:
conda install -c conda-forge ipyleaflet

from ipyleaflet import Map
# inserindo as coordenadas da Praça da Liberdade em Belo Horizonte, MG
Map(center=[-19.932252,-43.9381638], zoom=15)

BeakerX é um pacote com widgets destinados a representar tabelas, gráficos, formulários de modo interativo. O widget de tabela reconhece e exibe dataframes do pandas. Ele facilita a pesquisa, e permite classificar, arrastar, filtrar, formatar ou selecionar células na tabela. Ele também permite a exportação para CSV ou área de transferência e a representação gráfica dos dados. O widget de tabela é mostrado abaixo, em imagem estática. Mais informações em Beakerx.

# Instalação do pacote
conda install -c conda-forge beakerx ipywidgets

import pandas as pd
from beakerx import *
pd.read_csv("UScity.csv")
# Instalando o pacote
conda install -c conda-forge k3d	

import k3d
plot = k3d.plot()
plot += k3d.points([0, 0, 0, 1, 1, 1])
plot.display()

A figura dentro do Notebook é dinâmica, podendo ser girada, ampliada, exportada, etc.

Modificando o estilo do Notebook

Jupyter Notebooks são enviados para o navegador como páginas html e são formatadas e estilizadas por meio de arquivos css (cascade style sheets) padrões. Para quem conhece html e css pode ser interessante editar diretamente esse arquivo. (Não se esqueça de fazer cópias de segurança dos arquivos que vai modificar).

Se sua instalação não contém a pasta “.jupyter” execute o comando jupyter notebook --generate-config no terminal. No Linux o arquivo de estilos está em /home/usuario/.jupyter/custom/custom.css, no Windows em pasta.do.python\Lib\site-packages\notebook\static\custom\custom.css, embora isso possa ser alterado ou ser diferente para outras instalações. Para ver a pasta onde está o arquivo de configurações você pode usar:

import jupyter_core
jupyter_core.paths.jupyter_config_dir()

Existem muitas folhas de estilo prontas para baixar, por exemplo no GitHub. Elas podem ser baixadas e copiadas para a pasta citada.

Para modificar apenas o estilo do Notebook aberto, sem alterar a aparência dos demais você pode usar:

# Importe biblioteca 
from IPython.core.display import HTML
HTML("<style> instruções de estilo </style>")
# Alternativamente se pode usar "mágica" em uma célula
%%html "<style> estilos - CSS </style>"

# Por exemplo, para atribuir margens laterais de 10% do tamanho da página use
display(HTML("<style>.container { margin: 0 10% 0 10% !important; }<style>"))

# Para exibir outputs em negrito
display(HTML("<style>.output_wrapper { font-weight: bold !important; }</style>"))

De dentro de um Notebook você pode carregar uma folha de estilo particular que passa a valer para aquele documento. Esse método é útil para formatar documentos de um tipo específico, por exemplo para uma apresentação ou páginas com estilos incomuns.

from IPython.core.display import HTML
def css_styling():
    styles = open("../styles/custom.css", "r").read()
    return HTML(styles)
css_styling()

Finalmente você pode instalar extensões tais como Jupyter themes.

Exportando e Compartilhando Notebooks


Notebooks podem ser exportados para diversos formatos tais como HTML e PDF e vários outros formatos. Isso pode ser encontrado no menu File | Download as. Se estiver trabalhando na nuvem a importação no formato nativo .ipynb serve para baixar para seu computador uma cópia do notebook. Ao subir uma cópia para o GitHub ou para o Colab você pode disponibilizar a sessão para outros colaboradores. Slides são úteis para aulas e apresentações.

NBViewer: NBViewer é um renderizador de notebooks, útil para quem quer ver uma sessão sem ter que necessariamente instalar o Jupyter. Esse é um serviço gratuito oferecido pelo Projeto Jupyter, disponível em nbviewer.jupyter.org. Ele recebe uma URL do notebook e o renderiza como página da web.

Bibliografia

  • Wintjen, Marc: Practical Data Analysis Using Jupyter Notebook: Learn how to speak the language of data by extracting useful and actionable insights using Python, Packt Publishing, Birmingham, UK, 2020.
  • Taieb, David: Thoughtful Data Science, , Packt Publishing, Birmingham, UK, 2018.
  • Dataquest: Advanced Jupyter Notebooks Tutorial, acessado em novembro de 2020.
  • Real Python: Jupyter Notebook Introduction, acessado em novembro de 2020.
  • Jupyter.org: Jupyter Main Site, acessado em novembro de 2020.
  • jupyter.org: Jupyter Widgets, acessado em novembro de 2020.

Marcação de Texto com Markdown

“O poeta é um fingidor. Finge tão completamente que chega a fingir que é dor a dor que deveras sente.”
Fernando Pessoa, Autopsicografia.

O que é Markdown

Markdown é uma linguagem de marcação de texto. Ele permite a insercão de marcas ou códigos em texto simples que são representados na exibição final em formatos sofisticados. Além disso ela permite a conversão para textos em HTML, que podem ser distribuídos pela web (ou localmente) e lidos por browsers. A marcação foi concebida para deixar o texto simples e legível. Markdown é software livre, disponível sob uma licença BSD de código aberto.

Títulos (Headings)

Para criar títulos use sinais # em frente ao texto (um para cada nível).

Markdown html Output obtido
# Título de nível 1 <h1>Título de nível 1</h1> (título de nível 1)
## Título de nível 2 <h2>Título de nível 2</h2> (título de nível 2)
### Título de nível 3 <h3>Título de nível 3</h3> (título de nível 3)
#### Título de nível 4 <h4>Título de nível 4</h4> (título de nível 4)
##### Título de nível 5 <h5>Título de nível 5</h5> (título de nível 5)
###### Título de nível 6 <h6>Título de nível 6</h6> (título de nível 6)

Alternativamente, é possível sublinhar com sinais de igual (===) para gerar um título de nível 1 e com traços (---) para gerar um título de nível 2:

Markdown Output
Título de nível 1
=================
(Título de nível 1)
Título de nível 2
—————–
(Título de nível 2)

Bastam dois sinais == ou -- para a representação de título.

Parágrafos

Parágrafos são linhas de texto separadas por linhas vazias.

Markdown html Output obtido
Markdown é simples.
Vou usá-lo sempre!
<p>Markdown é simples.</p>
<p>Vou usá-lo sempre!</p>
Markdown é simples.
Vou usá-lo sempre!

Quebra de linhas

Para quebrar uma linha termine a linha com 2 ou mais espaços (⎵ ⎵) seguidos de return ().

Markdown html Output obtido
Essa é a primeira linha. ⎵⎵↲
Essa é a segunda linha.
<p>Essa é a primeira linha.<br>
Essa é a segunda linha.</p>
Essa é a primeira linha.
Essa é a segunda linha.

Negrito e itálico

Texto em negrito é obtido com asteriscos ou traços baixos (underscores) envolvendo o texto a negritar. Letras dentro da palavra também podem ser negritadas com duplos asteriscos.

Markdown html Output obtido
Precisamos de **negrito**. Precisamos de <strong>negrito</strong>. Precisamos de negrito.
Precisamos de __negrito__. Precisamos de <b>negrito</b>. Precisamos de negrito.
abcdef**GH**ij abcdef<strong>GH</strong>ij abdcefGHij

Textos em itálico são obtidos com um asterisco ou uma traço baixo (underscores) envolvendo o texto. Letras dentro da palavra também podem ser tornadas itálicos com um asterisco.

Markdown html Output obtido
Texto *em itálico*. Texto <em>em itálico</em>. Texto em itálico.
Texto _em itálico_. Texto <i>em itálico</i>. Texto em itálico.
a*bcd*ef a<em>bcd</em>ef abcdef

Texto em negrito e itálico simultâneos são obtidos com três asteriscos ou traços baixos (underscores) envolvendo o texto. Letras dentro da palavra também podem ser negritadas com três asteriscos.

Markdown html Output obtido
Um texto ***muito importante***. Um texto <strong><em>muito importante</em></strong>. Um texto muito importante.
Um texto ___muito importante___. Um texto <b><i>muito importante</i></b>. Um texto muito importante.
Um texto __*muito importante*__. Um texto <i><b>muito importante</b></i>. Um texto muito importante.
Um texto **_muito importante_**. …idem… Um texto muito importante.
Texto***muito***importante. Texto<strong><em>muito</em></strong>importante. Textomuitoimportante.

Textos riscados são obtidos com dois “tils” ˜˜ anterior e posterior ao texto.

Markdown html Output obtido
Texto ˜˜riscado˜˜. Texto <del>riscado</del>. Texto riscado.

Linha horizontal

Linhas horizontais são inseridas com três ou mais asteriscos (***), ‘dashes’ (---), ou ‘underscores’ (___) em uma linha.

Markdown html Output obtido
*** <hr>

Blocos

Para criar blocos destacados (citações) coloque > na frente do parágrafo.

> “O poeta é um fingidor. Finge tão completamente que chega a fingir que é dor a dor que deveras sente.”
> ― Fernando Pessoa, Autopsicografia.

“O poeta é um fingidor. Finge tão completamente que chega a fingir que é dor a dor que deveras sente.”
― Fernando Pessoa, Autopsicografia.

Blocos destacados podem conter muitos parágrafos. Para isso basta inserir > em cada linha branca entre as parágrafos.

> “Nós, os mortais, alcançamos a imortalidade nas coisas que criamos em comum e que estão atrás de nós.”
>
> “A coisa mais bela que podemos experimentar é o mistério. Essa é a fonte de toda a arte e ciências verdadeiras.”
> ― Albert Einstein.

“Nós, os mortais, alcançamos a imortalidade nas coisas que criamos em comum e que estão atrás de nós.”
“A coisa mais bela que podemos experimentar é o mistério. Essa é a fonte de toda a arte e ciências verdadeiras”.
― Albert Einstein.

Blocos ou citações podem ser aninhadas. Para isso coloque >> na frente do parágrafo com segunda indentação.

> “Eu jamais iria para a fogueira por uma opinião minha pois não tenho certeza alguma. Porém, eu iria pelo
direito de ter e mudar de opinião, quantas vezes quisesse.”
>
>> ”Sem música a vida seria um erro.”
> ― Friedrich Nietzsche

“Eu jamais iria para a fogueira por uma opinião minha pois não tenho certeza alguma. Porém, eu iria pelo direito de ter e mudar de opinião, quantas vezes quisesse.”

“Sem música a vida seria um erro.”

― Friedrich Nietzsche

Listas

Listas ordenadas (numeradas) são criadas numerando-se as linhas, cada número seguido de ponto. Qualquer número serve para isso, embora o número da primeira linha marque o número usado na lista:

Markdown html Output obtido
1. Primeiro item
2. Segundo item
3. Terceiro item
4. Quarto item
<ol>
<li>Primeiro item</li>
<li>Segundo item</li>
<li>Terceiro item</li>
<li>Quarto item</li>
</ol>
  1. Primeiro item
  2. Segundo item
  3. Terceiro item
  4. Quarto item
1. Primeiro item
1. Segundo item
1. Terceiro item
  1. Indentado item 1
  1. Indentado item 2
1. Quarto item
<ol>
<li>Primeiro item</li>
<li>Segundo item</li>
<li>Terceiro item
<ol>
<li>Indentado item 1</li>
<li>Indentado item 2</li>
</ol>
</li>
<li>Quarto item</li>
</ol>
  1. Primeiro item
  2. Segundo item
  3. Terceiro item
    1. Indentado item 1
    2. Indentado item 2
  4. Quarto item

Observe que não é necessário usar a numeração correta nas listas. Alguns editores completam automaticamente esses números. A segunda indentação (ou lista dentro de outra lista) é gerada por espaços ou tab antes do item.

Listas não numeradas são criadas com os sinais *, + ou - antes dos itens.

Markdown html Output obtido
– Primeiro item
– Segundo item
– Terceiro item
– Quarto item
<ul>
<li>Primeiro item</li>
<li>Segundo item</li>
<li>Terceiro item</li>
<li>Quarto item</li>
</ul>
  • Primeiro item
  • Segundo item
  • Terceiro item
  • Quarto item
– Primeiro item
– Segundo item
– Terceiro item
  - Indentado item 1
  - Indentado item 2
– Quarto item
<ul>
<li>Primeiro item</li>
<li>Segundo item</li>
<li>Terceiro item
<ul>
<li>Indentado item 1</li>
<li>Indentado item 2</li>
</ul>
</li>
<li>Quarto item</li>
</ul>
  • Primeiro item
  • Segundo item
  • Terceiro item
    • Indentado item 1
    • Indentado item 2
  • Quarto item

Blocos de código

Uma anotação apropriada para código, principalmente de programação, é uma característica muito útil no markdown. A indentação correta para blocos de código pode ser conseguida com linhas precedidas por 4 espaços ( ) ou 1 tab. Dentro de uma lista essas linhas são precedidas por 8 espaços ou 2 tabs. Nesse contexto usamos o sinal para representar um espaço:

Markdown html Output obtido
⎵⎵⎵⎵def soma(a,b):
⎵⎵⎵⎵⎵⎵⎵⎵return a + b
⎵⎵⎵⎵print (soma(5, 7))
<pre>
  def soma(a,b):
    return a + b
  print (soma(5, 7))
</pre>
  def soma(a,b):
    return a + b
  print(soma(5, 7))

Alternativamente, blocos de código também podem ser colocados entre três acentos graves, backticks, ```:

Markdown Output obtido
```
function factorial(n) {
  if (n === 0) return 1; // 0! = 1
  return n * factorial(n – 1);
}
factorial(3); // returns 6}
```
function factorial(n) {
  if (n === 0) return 1; // 0! = 1
  return n * factorial(n – 1);
}
factorial(3); // returns 6}

Nesse formato de bloco de código é possível marcar o código com cores, de acordo com a linguagem selecionada (syntax highlighting). Essa característica depende do processador usado. No exemplo seguinte o código é marcado como JSON, o que é informado após o primeiro grupo de acentos graves.

Markdown Output obtido
``` json
{
  ”nome”: “José”,
  ”sobrenome”: “Garcia”,
  ”idade”: 45
}
```
{
  “nome”: “José”,
  “sobrenome”: “Garcia”,
  “idade”: 45
}

Para denotar texto em código, uma ou mais palavras, coloque-as entre acentos graves (backticks, `).

Markdown html Output obtido
Digite no prompt de comando: `nano`. Digite no prompt de comando: <code>nano</code>. Digite no prompt de comando: nano.

Imagens

Para acrescentar uma imagem use a seguinte sintaxe:


![Imagem 1](caminho/images/tux.png “Título a ser exibido”)

Tabelas

Tabelas são criadas usando-se a barra vertical | e hífens. Por exemplo, as três tabelas abaixo mostram o uso do código markdown a seguir

Tabela (1)

| Título 1 | Título 2 | T3 |
| ‐‐‐‐‐‐‐‐‐‐‐‐| ‐‐‐‐‐‐‐‐‐‐‐‐|‐‐-‐|
| célula 1, 1 | célula 1, 2 | 1 |
| célula 2, 1 | célula 2, 2 | 2 |

Tabela (2)

| Alinhamento | Alinhamento | Alinhamento |
| :‐‐‐‐‐‐‐‐‐‐ | :‐‐‐‐‐‐‐‐‐: | ‐‐‐‐‐‐‐‐‐‐: |
| Esquerda | Centro | Direita |
| 1 | 2 | 3 |

Tabela (3)

Título | N
-|-
a|b

são representadas como as tabelas (1), (2) e (3) abaixo:

A tabela (2) ilustra o uso dos sinais de alinhamento: à esquerda (:‐‐), centralizado (:‐‐:) (centralizado) e à direita (‐‐:). Os itens da tabela não precisam estar bem alinhados no markdown e as barras laterais são dispensáveis, como é mostrado na tabela (3). Dentro das tabelas o texto pode conter links, negrito e itálico e código.

Links

Links para outras páginas, locais ou na web, podem ser inseridos dentro de colchetes (ex.: [Duck Duck Go]) seguidos da URL entre parênteses. Por ex. (https://duckduckgo.com)).

Markdown html Output obtido
Recomendo o mecanismo de busca_ _
[Duck Duck Go](https://duckduckgo.com).
Recomendo o mecanismo de busca <br>
<a href=”https://duckduckgo.com”>Duck Duck Go</a>.
Recomendo o mecanismo de busca
Duck Duck Go.

Notas de Pés de Página

Segue um exemplo de texto com notas de pé de página.

Use a seguinte notação para criar pés de página[^1], que também pode conter textos mais longos e várias parágrafos[^2].
Os marcadores não precisam ser números[^t].[^1]: O pé de página fica no final do texto.
[^2]: Um pé de página pode ter vários parágrafos.
Todos os parágrafos dentro da mesma nota devem ser indentados.
`{ function teste(a, b): # pode conter código e outras formatações. }`
[^t]: Outro pé de página.

Esse código é apresentado em forma final da seguinte forma (com os links ativos):


Use a seguinte notação para criar pés de página[1], que também pode conter textos mais longos e várias parágrafos[2].
Os marcadores não precisam ser números[3].


1. O pé de página fica no final do texto.
2. Um pé de página pode ter vários parágrafos.
Todo o texto dentro do mesma anotação deve ser indentado.
{ function teste(a, b): # pode conter código e outras formatações. }
3. Outro pé de página.

Lista de tarefas

O código abaixo gera a lista mostrada à direita.

## Lista de Tarefas
- [x] Tarefa primeira (completa)
- [ ] Tarefa segunda
- [ ] Arranjar outras **tarefas**

Lista de Tarefas
☑ Tarefa primeira (completa)
Tarefa segunda
Arranjar outras tarefas

HTML

Vários aplicativos que usam Markdown, entre eles o Jupyter Notebook, também podem representar corretamente diversas tags de marcação html. Isso é útil para usuários familiarizados com html, principalmente quando se pretende inserir texto com formatos mais sofisticados, tais como como texto colorido, com variação de tamanho, caixas coloridas, etc.

Como exemplo, o código abaixo produzirá uma barra vermelha, útil para marcar o ponto de parada dentro de um notebook (Jupyter). Outro exemplo consiste na produção de texto sublinhado. Como não há código markdown específico para isso você pode conseguir texto sublinhado com as tags html.

<u>Texto 1 sublinhado</u> ou <ins>Texto 2 sublinhado</ins><div style=”border:1px solid black; padding:10px; color:white; background-color:red;”>parei aqui &curarr;</div>


Texto 1 sublinhado ou Texto 2 sublinhado

parei aqui ↷

Latex

LaTeX é uma linguagem de composição de texto para a produção de documentos científicos. Vários editores de Markdown podem renderizar equações matemáticas e símbolos Latex para reproduzir notação matemática. Em particular o Jupyter Notebook reconhece código LaTeX escrito nas células de Markdown e os representa corretamente no navegador usando a biblioteca JavaScript MathJax. Para inserir uma fórmula matemática em uma linha use o símbolo $expressão$. Para equações isoladas em uma linha e centralizadas use $$expressão$$.

A identidade de Euler é $e^{i \pi} + 1 = 0$.
As equações de Einstein são:
$$
R_{\mu\nu}-\frac{1}{2} Rg_{\mu\nu} +\Lambda g_{\mu\nu} = \frac{8 \pi G}{c^4} T_{\mu\nu}.
$$
A definição da derivada de primeira ordem é:
$$
f'(x) = \lim\limits_{h \rightarrow 0} \frac{f(x+h) – f(x)}{h}
$$

A identidade de Euler é \(e^{i \pi} + 1 = 0\).
As equações de Einstein são:
$$
R_{\mu\nu}-\frac{1}{2} Rg_{\mu\nu} +\Lambda g_{\mu\nu} = \frac{8 \pi G}{c^4} T_{\mu\nu}.
$$
A definição da derivada de primeira ordem é:
$$
f'(x) = \lim\limits_{h \rightarrow 0} \frac{f(x+h) – f(x)}{h}
$$

Editores de Markdown

Existem muitos bons editores de markdown. Algumas sugestões:

Remarkable
Haroopad
UberWriter
Macdown
Ghostwriter
Marcor

Editores online:

São excelentes aplicativos de anotações, permitindo o uso de plugins para calendários, ToDos, customização de aparência por css. Permitem a sincronização entre diversos aparelhos Mac, Windows, Linux e Androide:

SimpleNote, Joplin, Obsidian

Bibliografia