Tutorial – Compreensão de listas Python, com exemplos

Traduzido de Tutorial – Python List Comprehension With Examples

Autor: AARSHAY JAIN

Introdução

Em Python, Compreensão de Listas é um conceito poderoso e que deve ser estudado. No entanto, ainda é um dos tópicos mais difíceis para quem está se iniciando em Python. Com este post, pretendo ajudar você que está enfrentando dificuldades. Dominar esse conceito vai ajudá-lo de duas maneiras:

  • Você pode escrever códigos mais curtos e mais eficazes
  • Como consequência, seu código será executado de forma mais rápida

Você sabia que as Compreensões de Lista são 35% mais rápidas do que o loop FOR e 45% mais rápidas do que a função map? Eu descobri esses e muitos outros fatos interessantes ao escrever este post.

Se você está lendo esse post, tenho certeza que quer aprender do zero ou aprimorar-se nesse conceito. Em ambos casos, este post irá ajudá-lo.

P.S – Este artigo é mais adequado para usuários iniciantes e intermediários do Python for Data Science. Se você já é um usuário experiente em python, este post pode não ajuda-lo muito.

Tabela de conteúdo

  1. O que é Compreensão de Listas (CL)?
  2. Vantagem em tempo
  3. Compreensão de Listas como geradores
  4. Algumas aplicações em Data Science

1. O que é Compreensão de Listas (CL)

Quando comecei a usar a CL pela primeira vez, me recordei da notação de conjuntos. Sim, isso mesmo. Notação de conjuntos é um conceito ensinado principalmente na classe XI na Índia. (verifique isso). Escolhi alguns exemplos fáceis desse livro-texto para explicar o conceito.

Vejamos alguns exemplos simples:

  1. {x ^ 2: x é um número natural menor que 10}
  2. {x: x é um número inteiro menor que 20, x é ímpar}
  3. {x: x é uma letra na palavra ‘MATEMÁTICA’, x é uma vogal}

Agora vejamos os códigos Python correspondentes em CL, na mesma ordem:

  1. [x**2 for x in range(0, 10)]
  2. [x for x in range(1, 20) if x % 2 == 0]
  3. [x for x in ‘MATEMÁTICA’ if x in [‘A’, ‘E’, ‘I’, ‘O’, ‘U’]]

Parece intuitivo, certo?

Não se preocupe se parecer um pouco confuso. Continue me acompanhando, vai ficar mais claro na medida em que avançamos. Vamos dar um passo atrás e analisar o que está acontecendo aqui. Cada um dos exemplos acima envolve 3 coisas – iteração, filtragem condicional e processamento. Por isso, talvez você possa dizer que se trata apenas de uma outra representação de loop FOR.

Em termo gerais, um loop FOR funciona assim:

for (conjunto de valores a iterar):
if (filtro condicional):

expressão_de_retorno()
O mesmo código pode ser construído de modo CL numa única linha:
[ expressão_de_retorno() for (conjunto de valores a iterar) if (filtro condicional) ]

Considere agora outro exemplo: {x: x é um número natural menor ou igual a 100, x é uma raiz quadrada exata}. Isso pode ser resolvido com um loop FOR da seguinte forma:

for i in range(1, 101):         #iterador
if int(i**0.5) == i**0.5:
#filtro condicional
print i #retorno

Agora, para criar o código CL, precisamos apenas plugar as diferentes partes:

[ i for i in range(1, 100) if int(i**0.5) == i**0.5 ]

Espero que agora esteja fazendo mais sentido. Quando você se acostumar, verá que CL é uma técnica simples mas poderosa, e que pode ajudá-lo com uma série de tarefas. Algumas coisas para ter em mente:

  • CL sempre irá retornar um resultado, mesmo que você não o utilize
  • As expressões de iteração e de condição podem ser niveladas em diversas instâncias
  • Mesmo uma CL pode ser incluída em outra CL
  • Múltiplas variáveis podem ser iteradas e manipuladas ao mesmo tempo

Vamos olhar alguns outros exemplos para entender em mais detalhes.

Talvez existam funções pré-definidas para executar as tarefas delineadas abaixo, mas ainda assim servem de bons exemplos para entender CL.

Nota: todos os meus códigos serão confinados a funções. Isso geralmente é uma boa prática pois melhora a re-usabilidade, e também por um motivo especial que mostrarei a seguir.

Exemplo 1: Horizontalizar uma matriz

Objetivo: pegar uma matriz e retornar uma lista onde cada linha segue a outra.

Códigos Python do loop FOR e da CL:

def eg1_for(matriz):
flat = []
for row in matriz:
for x in row:

flat.append(x)
return flat

def eg1_cl(matriz):
return [x for row in matriz for x in row]

Vamos definir a matriz e testar os resultados:

matriz = [ range(0, 5), range(5, 10), range(10, 15) ]
print "Matriz original: " + str(matriz)
print "Loop FOR : " + str(eg1_for(matriz))
print "CL : " + str(eg1_cl(matriz))

Resultado:

Matriz original : [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]
Loop FOR : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
CL : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

Exemplo 2: Removendo vogais de uma frase

Objetivo: pegar uma ‘string’ como input e retornar uma ‘string’ sem as vogais.

Códigos Python do loop FOR e da CL:

def eg2_for(sentenca):
vogais = 'aeiou'
lista_filtrada = []
for s in sentenca:
for s not in vogais:
lista_filtrada.append(s)
return ''.join(lista_filtrada)

def eg1_cl(sentenca):
vogais = 'aeiou'
return ''.join([s for s in sentenca if s not in vogais])

Vamos definir a sentença e testar os resultados:

sentenca = 'My name is Aarshay Jain!'
print "Loop FOR : " + str(eg2_for(sentenca))
print "CL : " + str(eg2_cl(sentenca))

Resultado:

Loop FOR        : My nm s Arshy Jn!
CL : My nm s Arshy Jn!

Exemplo 3: Compreensão de dicionário

Objetivo: pegar duas listas de mesmo tamanho como input e retornar um dicionário em que uma lista forma as chaves e a outra lista forma os valores.

Códigos Python do loop FOR e da CL:

def eg3_for(chaves, valores):
dic = {}
for i in range(len(chaves)):
dic[chaves[i]] = valores[i]
return dic

def eg3_cl(chaves, valores):
return { chaves[i]: valores[i] for i in range(len(chaves)) }

Vamos definir as listas e testar os resultados:

pais = ['India', 'Paquistão', 'Nepal', 'Butão', 'China', 'Bangladesh']
capital = ['Nova Delhi', 'Islamabad', 'Kathmandu', 'Thimphu' 'Pequim', 'Dhaka']
print "Loop FOR : " + str(eg3_for(pais, capital))
print "CL : " + str(eg3_cl(pais, capital))

Resultado:

Loop FOR        : {'India': 'Nova Delhi', 'Paquistão': 'Islamabad', 'Nepal': 'Kathmandu', 'Butão': 'Thimphu', 'China': 'Pequim', 'Bangladesh': 'Dhaka'}
CL : {'India': 'Nova Delhi', 'Paquistão': 'Islamabad', 'Nepal': 'Kathmandu', 'Butão': 'Thimphu', 'China': 'Pequim', 'Bangladesh': 'Dhaka'}

Creio que o esquema de cores ajuda a tornar as coisas auto-explanatórias. Até agora, focamos num único aspecto de CL, i.e., sua brevidade e facilidade de leitura. Mas isso não é tudo. CLs são mais rápidas em vários cenários quando comparadas com as alternativas disponíveis. Vamos continuar explorando.

2. Vantagem em tempo

Nessa seção, vamos ver o tempo de processamento da CL em comparação com outras técnicas. Vamos tentar também mostrar as situações em que a CL funciona melhor e as em que deveria ser evitada. Junto com tempo de processamento, vamos comparar a facilidade de leitura das diferentes abordagens.

Antes de começar com as comparações, vamos revisitar a função map do Python

Função map

A função map é usada para aplicar uma função a cada elemento de uma lista ou de qualquer outro item iterável.

Sintaxe: map(função, iterável Python)

Por exemplo, podemos multiplicar cada elemento de uma lista de inteiros pelo número seguinte, usando esse código:

map(lambda x: x * (x + 1), arr)

Comparando o tempo de processamento

Uma parte importante deste exercício é poder comparar o tempo de processamento de fragmentos de código. Iremos usar %timeit, uma funcão mágica nativa do ambiente do notebook iPython. Opcionalmente, você pode usar os módulos time ou timeit.

Iremos comparar os tempos de processamento de produção dos mesmos resultados ao usar CL, loop FOR e função map. Ademais, daremos foco nos tempos de processamento relativos e não nos valores absolutos, pois estes estão sujeitos à especificações de máquina.

Um exemplo simples

Vamos começar com um exemplo simples – elevar ao quadrado cada elemento de uma lista. Os códigos Python e os tempos de processamento para cada uma das três implementações são:

#Método 1: Loop FOR
def raiz_for(arr):
resultado = []
for i in arr:
resultado.append(i***2)
return resultado
%timeit raiz_for(range(1, 11))

Resultado: 100.000 loops, melhor de 3: 2,69 µs por loop

#Método 2: Função MAP
def raiz_map(arr):
return map(lambda x: x**2, arr)
%timeit raiz_map(range(1, 11))

Resultado: 100.000 loops, melhor de 3: 3,16 µs por loop

#Método 3: CL
def raiz_cl(arr):
return [i**2 for i in arr]
%timeit raiz_cl(range(1, 11))

Resultado: 100.000 loops, melhor de 3: 1,67 µs por loop

Tempo de processamento: Vemos claramente que nesse caso a CL é 35% mais rápida que loop FOR e 45% do que a função map.

Facilidade de leitura: Ambas CL e função map são simples de ler, o loop FOR é mais verboso embora não muito.

Então podemos afirmar que CL é o método mais rápido? Não necessariamente! Não podemos generalizar nossas conclusões no estágio atual pois pode ser muito específico ao problema em questão. Vamos ver a seguir um exemplo mais engenhoso.

Dando um passo adiante

Vamos jogar uma casca de banana para o problema. E se quisermos apenas elevar ao quadrado os números pares? Agora, as três funções ficariam assim:

#Método 1: Loop FOR
def raiz_for(arr):
resultado = []
for i in arr:
if i % 2 == 0:
resultado.append(i***2)
return resultado
%timeit raiz_for(range(1, 11))

Resultado: 100.000 loops, melhor de 3: 2,31 µs por loop

#Método 2: Função MAP
def raiz_map(arr):
return filter(lambda x: x is not None, map(lambda x: x**2 if x % 2 == 0 else None, arr))
%timeit raiz_map(range(1, 11))

Resultado: 100.000 loops, melhor de 3: 5,25 µs por loop

#Método 3: CL
def raiz_cl(arr):
return [i**2 for i in arr if i % 2 == 0]
%timeit raiz_cl(range(1, 11))

Resultado: 100.000 loops, melhor de 3: 1,67 µs por loop

Tempo de processamento: CL é 25% mais rápida que loop FOR e 65% do que a função map.

Facilidade de leitura: CL é a mais concisa e elegante. A função map ficou muito confusa com a função lambda adicional e o loop FOR não é muito melhor.

Por que a CL é mais rápida? Afinal, cada método está fazendo a mesma coisa e na mesma escala – iteração, filtragem condicional e execução. Então o que torna a CL especial? CL será mais rápida em todos os cenários ou esses são casos especiais? Vamos descobrir!

Chegando ao X da questão

Vamos comparar cada algoritmo em 3 itens:

  • Iterando a lista
  • Modificando cada elemento
  • Armazenando o resultado

Primeiro, vamos chamar uma função simples que não tem obrigação de fazer nada e cujo papel primário é iteração. O código e tempo de processamento de cada método segue:

#Método 1: Loop FOR
def vazio_for(arr):
for i in arr:
pass
%timeit vazio_for(range(1, 11))

Resultado: 1.000.000 loops, melhor de 3: 714 ns por loop

#Método 2: Função MAP
def vazio_map(arr):
map(lambda x: None, arr)
%timeit vazio_map(range(1, 11))

Resultado: 1.000.000 loops, melhor de 3: 2,29 µs por loop

#Método 3: CL
def vazio_cl(arr):
[None for i in arr]
%timeit vazio_cl(range(1, 11))

Resultado: 1.000.000 loops, melhor de 3: 1,18 µs por loop

Tempo de processamento: Loop FOR é o mais rápido. Isto porque num loop FOR, não precisamos retornar um elemento e apenas seguimos para a próxima iteração usando “pass”. Em ambos CL e map, retornar um elemento e necessário. O código aqui retorna None. Ainda assim, map leva quase o dobro do tempo. Intuitivamente, podemos imaginar que map envolve uma chamada definitiva de função a cada iteração, o que pode explicar o tempo extra. Vamos explorar isso mais tarde.

Por ora, vamos rodar uma simples operação de multiplicar um número por 2 mas sem ter que armazenar o resultado.

#Método 1: Loop FOR
def x2_for(arr):
for i in arr:
i * 2
%timeit x2_for(range(1, 11))

Resultado: 1.000.000 loops, melhor de 3: 1,07 µs por loop

#Método 2: Função MAP
def x2_map(arr):
map(lambda x: x * 2, arr)
%timeit x2_map(range(1, 11))

Resultado: 1.000.000 loops, melhor de 3: 2,61 µs por loop

#Método 3: CL
def x2_cl(arr):
[i * 2 for i in arr]
%timeit x2_cl(range(1, 11))

Resultado: 1.000.000 loops, melhor de 3: 1,44 µs por loop

Tempo de processamento: Aqui vemos uma tendência similar às anteriores. Então, até o ponto em que iteramos e fazemos pequenas modificações, o loop FOR é o claro vencedor. CL fica perto do loop FOR e a função map novamente leva quase o dobro do tempo. Note que aqui a diferença no tempo de processamento também vai depender da complexidade da função que se aplica a cada elemento.

Intuitivamente podemos dizer que o tempo de processamento maior para a CL e a função map se dá porque ambos os métodos armazenam informações e na verdade executam os três passos. Por isso, vamos checar o tempo de processamento do loop FOR com o terceiro passo:

def armazena_for(arr):
result=[]
for i in arr:
result.append(i*2)r
return result
%timeit store_for(range(1,11))

Resultado: 1.000.000 loops, melhor de 3: 2,55 µs por loop

Interessante! O tempo de processamento do loop FOR aumentou em quase 2,5 vezes, só por conta de armazenar informação. Isso porque temos que definir uma lista vazia a adicionar o resultado a cada iteração.

Depois dos três resultados, CL parece ser o vencedor. Mas você tem 100% de certeza disso? Não sei quanto a você, mas eu ainda não estou convencido. Ambas CL e map tem uma implementação mais elegante (nesses casos) e não consigo ver a razão do map ser tão lento.

Uma possibilidade é porque map tem que chamar a função a cada iteração. Enquanto CL pode estar apenas calculando o valor da mesma expressão para todos os elementos. Podemos validar isso rapidamente. Vamos fazer uma chamada compulsória de função na CL também:

def x2_cl(arr): 
def mul(x):
return x*2
[mul(i) for i in arr]
%timeit x2_lc(range(1,11))

Resultado: 1.000.000 loops, melhor de 3: 2,67 µs por loop

Então, minha intuição estava certa. Quando forçamos a CL a chamar funções, ela leva quase o mesmo tempo que map.

Vamos resumir nossos achados até agora:

  • CL é rápida e elegante em casos que envolvem expressões simples. Mas caso funções complexas são necessárias, map e CL tem performance similar
  • Loop FOR é mais pesada em geral, só que mais lenta se precisar armazenar valores. Então deveria ser usada em casos onde apenas se precisa iterar e executar operações

Comparei o tempo de processamento dos 3 exemplos que vimos até agora e os resultados são esses:

S.No Exemplo Tempo Proc. FOR (µs) Tempo Proc. CL (µs)
1 Matriz horizontal 2,95 1,35
2 Remoção de vogais 4,00 2,49
3 Compreensão de dicionário 1,57 2,16

Nos primeiros dois casos, CL é mais rápida porque há necessidade de armazenar valores. Contudo, no caso 3 o loop FOR é mais rápido porque envolve compreensão de dicionário. Como dicionários são implementados como tabelas hash, adicionar novos valores é muito mais rápido do que numa lista. Então vemos que os três primeiros exemplos têm similaridades com nossas conclusões iniciais.

Vamos explorar mais?

  • Dado um número N, encontre todos números primos menores que N
  • Multiplique duas matrizes
  • Dado um inteiro N, encontre todos os triângulos com comprimentos únicos que podem ser formados com lados menores ou iguais a N

Agora, vamos olhar uma outra faceta da CL, a qual irá permitir que você a torne ainda mais rápida em certas aplicações específicas.

3. Gerador de expressões (GE)

Nunca ouviu falar de geradores no Python? Vamos tentar entender.

Um gerador é um iterador que retorna um único valor por vez e mantém a contagem da iteração entre as chamadas. É como os arremessos de bola no Cricket. Os passos são:

  1. Inicialização: o capitão do time decide qual arremessador vai arremessar e o arremessador sabe que deve jogar 6 bolas
  2. Bola 1: o arremessador arremessa pela primeira vez, o rebatedor rebate a bola, um jogador de campo pega a bola e joga para um jogador de área
  3. Bolas 2-5: procedimento similar é executado
  4. Bola 6: esse é o arremesso final e o arremessador sabe que vai parar na sequência

Um gerador trabalha de modo similar. Os pontos chave para ter em mente sobre um gerador:

  1. Assim como um arremessador de Cricket sabe que deverá arremessar 6 bolas, um gerador sabe o número de iterações (pode até ser infinito mas ele saberá disso)
  2. Assim como um arremessador não arremessa seis bolas de uma vez, o gerador também retorna os valores um por vez a cada chamada
  3. Assim como um novo arremessador é chamado após cada ‘over’, um gerador tem que ser reinicializado uma vez que as iterações tenham terminado

Vamos considerar um exemplo simples – gerar inteiros de 0 ao número N passado como argumento ao gerador.

#Definição do gerador
def meu_primeiro_ger(n):
for i in range(n):
yield i

Agora, o que acontece quando imprimimos i? (Não se preocupe com a palavra-chave yield. Vamos chegar nela daqui a pouco.)

print meu_primeiro_gen(10)

Não retorna uma lista como num loop FOR tradicional. Na verdade, retorna apenas um objeto. Funciona de modo que precisamos determinar uma variável para esse objeto e então obter os números usando next. No exemplo a seguir, o fluxo seria:

gen = meu_primeiro_ger(3) #Inicia o gerador
print gen.next()

Resultado: 0

print gen.next()

Resultado: 1

print gen.next()

Resultado: 2

print gen.next()

Espero que esteja fazendo sentido. Mas como vai-se saber qual valor retornar? É aí que entra o yield. Qualquer que seja o next a ser chamado, o gerador irá executar até encontrar yield. O valor escrito após yield será retornado e a execução interrompida. Caso yield não seja encontrado, retorna erro. Vamos considerar outro exemplo e ver o fluxo de informação:

def fluxo_de_info_ger(N):
print 'função roda pela primeira vez'
for i in range(N):
print 'execução antes do valor de yield %d' %i
yield i
print 'execução depois do valor de yield %d' %i
print 'função roda pela última vez'

Os resultados para as chamadas subsequentes são:

In [1]
gen2 = fluxo_de_info_ger(3)
gen2.next()

Out[1]
função roda pela primeira vez
execução antes do valor de yield 0
0
In [2]
gen2.next()

Out[2]
execução depois do valor de yield 0
execução antes do valor de yield 1
1
In [3]
gen2.next()

Out[3]
execução depois do valor de yield 1
execução antes do valor de yield 2
2
In [4]
gen2.next()

Out[4]
execução depois do valor de yield 2
função roda pela última vez

Espero que as coisas estejam mais claras agora. Se você pensar em termos de ‘overs’ no cricket, começará a fazer sentido. Há apenas um problema, não sabemos quando termina a não ser quando retorna erro. Uma solução seria usar ‘try and except‘ e checar o erro StopIteration. Mas o Python automaticamente gerencia isso quando geradores são chamados de funções que aceitam um iterador. Por exemplo:

gen3 = my_first_gen(10) 
print sum(gen3)

Resultado: 45

Nota: isso começa no estado atual e vai até o final. Se um next é chamado antes de um sum, não levará em conta os números já retornados. Exemplo:

gen3 = meu_primeiro_ger(10) 
gen3.next()
gen3.next()
gen3.next()
gen3.next()
sum(gen3)

Resultado: 39

Tendo entendido o básico sobre geradores, chegamos a Geradores de Expressões (EG). Em essência, faz-se simplesmente ao trocar [] por (). Por exemplo:

#Implementação CL
[x for x in range(10)]

Resultado: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

#Implementação EG
(x for x in range(10))

Resultado: at 0x0000000003F7A5A0>

Novamente, podemos aplicar uma função que toma um iterável como argumento para a CL:

sum(x for x in range(10))

Resultado: 45

Gerador é um conceito muito útil e eu o encorajo a explorar mais a seu respeito.

Por que usar Geradores em Python?

Você deve estar se perguntando por quê alguém deveria usar um gerador! Funciona um por um, tem alguns atributos inerentes que devem ser lembrados e não é compatível com muitas funções e mesmo com bibliotecas adicionais.

A grande vantagem é que geradores demandam muito pouca memória ao retornar números. Por outro lado, usar uma simples CL primeiro vai criar uma lista na memória e depois somá-la. Contudo, geradores podem não ter uma boa performance em problemas menores porque fazem muitas chamadas de função, tornando-o mais lento. Deveria ser usado em resoluções computacionais que são muito pesadas na memória.

Considere as seguintes funções:

def sum_list(N):
return sum([x for x in range(N)])
def sum_gen(N):
return sum((x for x in range(N)))

Rodei essas funções para 3 valores diferentes de N e os tempos de processamento são ilustrados abaixo:

Conclusões:

  1. Para um N pequeno, GE é relativamente mais lento que CL. Isso porque alto número de chamadas são necessárias na GE mas CL não demanda muito espaço em memória.
  2. Para um N moderado, GE e CL tem quase que a mesma performance. GE demanda um alto número de chamadas a CL precisa de um bloco de memória grande.
  3. Para um N muito elevado, geradores são drasticamente poderosos pois CL leva ~70 vezes o tempo levado pelo GE. Isso porque CL usa um pedaço enorme de RAM e o processamento fica muito lento.

4. Usando esse conceito em Data Science

Chegando ao fim, vamos discutir algumas aplicações de Data Science para esses imensamente úteis conceitos. Recapitulando rapidamente, vamos relembra a estrutura geral da CL:

[ expressão_de_retorno() for (conjunto de valores a iterar) if (filtro condicional) ]

Exemplo 4: lendo uma lista de listas

Você já deve ter encontrado situações em que cada célula de uma coluna de um conjunto de dados contém um lista. Tentei simular essa situação usando um exemplo simples baseado nesses dados. Eles contém um dataframe com 2 colunas:

  1. personID: identificador único por pessoa
  2. skills: os diferentes jogos uma pessoa pode jogar
Vamos carregar os dados:
import pandas as pd
data = pd.read_csv("skills.csv")
print data

Aqui, você pode ver os diferentes esportes que uma pessoa pratica agrupados na mesma coluna. Para tornar esses dados úteis para um modelo preditivo, em geral é uma boa ideia criar uma nova coluna para cada esporte e marcar como 1 ou 0 dependendo da pessoa praticar ou não.

O primeiro passo é converter o texto numa lista para que entradas individuais possam ser avaliadas.

#Separa o texto com delimitador ';'
data['skills_list'] = data['skills'].apply(lambda x: x.split(';'))
print data['skills_list']

A seguir, precisamos de uma lista de esportes únicos para identificar o número de colunas necessária. Conseguimos isso através de compreensão de sets. Sets são uma coleção de elementos únicos.

#Inicializa o set 
skills_unq = set() #Atualiza cada entrada do set. Como somente aceita um valor único, duplicidades serão automaticamente ignoradas.
skills_unq.update( (sport for l in data['skills_list'] for sport in l) )
print skills_unq
set(['badminton', 'cricket', 'football', 'tabletennis', 'volleyball'])

Note que aqui usamos um gerador de expressão para que cada valor seja atualizado e não haja necessidade de armazenamento. Agora iremos criar uma matriz usando CL contendo 5 colunas com tags 0 ou 1 correspondendo a cada esporte.

#Converter set em lista: 
skills_unq = list(skills_unq)
sport_matrix = [ [1 if skill in row else 0 for skill in skills_unq] for row in data['skills_list'] ]
print sport_matrix

Note que aqui novamente uma instância de CL foi agrupada em outra. A CL interna tem as chaves em negrito. Por favor acompanhe a convenção de cores para entendimento. Caso ainda não esteja claro, recomendo tentar um loop FOR e então converter para CL. Sinta-se a vontade para deixar um comentário no post caso ainda esteja encontrando dificuldades nesse estágio.

O último passo é converter isso numa coluna de dataframe do Pandas, dessa forma:

data = pd.concat([data, pd.DataFrame(sport_matrix,columns=skills_unq)],axis=1) 
print data

Exemplo 5: Criando exponenciação de colunas para uma regressão polinomial

Algoritmos de regressão polinomial requerem exponenciação múltipla da mesma variável, o que pode ser criado usando CL. Uma mesma variável pode ser exponenciada 15 a 20 vezes e pode ser modelada usando regressão de Ridge para reduzir o sobreajuste (“overfit”).

Vamos criar um conjunto de dados com uma coluna:

data2 = pd.DataFrame([1,2,3,4,5], columns=['number']) 
print data2

number
0 1
1 2
2 3
3 4
4 5

Vamos definir uma variável “deg” contendo o número de exponenciações requeridas. Manter as coisas dinâmicas é geralmente uma boa prática, dá flexibilidade. O primeiro passo é criar uma matriz contendo diferentes expoentes da variável “number”.

#Define o grau: 
deg=6 #Cria a matriz:
power_matrix = [ [i**p for p in range(2,deg+1) ] for i in data2['number'] ]
print power_matrix

Agora iremos somar isso ao dataframe similar ao exemplo anterior. Note que precisamos de uma lista de nomes de colunas nesse caso e, de novo, podemos usar CL para obtê-la facilmente.

cols = ['power_%d'%i for i in range(2,deg+1)] 
data2 = pd.concat([data2, pd.DataFrame(power_matrix,columns=cols)],axis=1)
print data2

E assim as colunas exponenciadas foram somadas com sucesso.

Exemplo 6: Filtrando nomes de colunas

Pessoalmente, já tive que lidar muitas vezes com a questão de selecionar um subconjunto de colunas num dataframe com o objetivo de arrumar previsores num modelo preditivo. Vamos considerar uma situação onde total de colunas é:

cols = ['a','b','c','d','a_transform','b_transform','c_transform','d_power2','d_power3','d_power4','d_power5','temp1','temp2']

Estas podem ser entendidas como:

  1. a,b,c,d: colunas originais nos dados brutos
  2. a_transform, b_transform, c_transform: funcionalidade adicionadas após aplicar certas transformações como logaritmo, raiz quadrada, combinação catgórica, etc.
  3. d_power2, d_power3, d_power4, d_power5: diferentes graus de exponenciação da mesma variável para regressão polinomial
  4. temp1, temp2: variáveis intermediárias criadas para executar certas certos cálculos

Dependendo da análise sendo feita ou do modelo sendo usado, podem haver diferentes casos de uso:

  1. Seleciona somente variáveis transformadas
  2. Seleciona diferentes graus de exponenciação da mesma variável
  3. Seleciona uma combinação das duas acima
  4. Seleciona todas as variáveis exceto as temporárias

Isso pode ser feito com o código abaixo.

col_set1 = [ x  for x in cols if x.endswith('transform')] 
col_set2 = [ x for x in cols if 'power' in x]
col_set3 = [ x for x in cols if (x.endswith('transform')) | ('power' in x)]
col_set4 = [ x for x in cols if x not in ['temp1','temp2']]
print 'Set1: ', col_set1
print 'Set2: ', col_set2
print 'Set3: ', col_set3
print 'Set4: ', col_set4

Notas finais

Isso me traz ao fim deste tutorial. Aqui mergulhamos a fundo na Compreensão de Listas do Python. Aprendemos sobre a vantagem de tempo da CL, comparamos a CL com função map e loop FOR, vimos Geradores de Expressões e exploramos algumas aplicações avançadas.

Se você chegou até aqui, acredito que agora você possa apreciar melhor a importância da CL. Você deveria tentar incorporar essa técnica aos eus códigos. Talvez consuma um pouco de tempo no começo, mas acredite em mim que você irá aproveitar.

Achou esse artigo útil? Ficou faltando algo? Você tem alguma aplicação onde a CL é útil? Por favor compartilhe conosco seus comentários e ficaremos felizes em discutir mais.


Veja também: