Python For Excel Traduzido
Python For Excel Traduzido
for Excel
A Modern Environment for Automation
and Data Analysis
Felix Zumstein
Python for Excel
While Excel remains ubiquitous in the business world,
recent Microsoft feedback forums are full of requests to “This book explains
include Python as an Excel scripting language. In fact, it’s how you can integrate
the top feature requested. What makes this combination so
Python into Excel and
compelling? In this hands-on guide, Felix Zumstein—creator
free yourself from the
of xlwings, a popular open source package for automating
inevitable disaster
Excel with Python—shows experienced Excel users how to
of huge workbooks,
integrate these two worlds efficiently.
thousands of formulas,
Excel has added quite a few new capabilities over the past and ugly VBA hacks.
couple of years, but its automation language, VBA, stopped Python for Excel is
evolving a long time ago. Many Excel power users have probably the single
already adopted Python for daily automation tasks. This most useful book on
guide gets you started.
Excel that I have read
• Use Python without extensive programming knowledge and an absolute must-
read for any advanced
• Get started with modern tools, including Jupyter notebooks
and Visual Studio Code
Excel user.”
—Andreas F. Clenow
• Use pandas to acquire, clean, and analyze data and replace CIO, Acies Asset Management, and
typical Excel calculations author of international best-sellers
Following the Trend, Stocks on the Move,
• Automate tedious tasks like consolidation of Excel and Trading Evolved
workbooks and production of Excel reports
• Use xlwings to build interactive Excel tools that use Python
as a calculation engine Felix Zumstein is creator and
maintainer of xlwings, a popular
• Connect Excel to databases and CSV files and fetch data open source package that allows the
from the internet using Python code automation of Excel with Python on
Windows and macOS. As CEO of xltrail,
• Use Python as a single tool to replace VBA, Power Query,
a version control system for Excel files,
and Power Pivot he’s gained deep insight into the typical
use cases and issues with Excel across
various industries.
What can Python do for Excel? If you’ve ever dealt with unexpected workbook crashes,
broken calculations, and tedious manual processes, you’ll want to find out. Python for
Excel is a comprehensive and succinct overview to getting started with Python as a
spreadsheet user, and building powerful data products using both. Don’t let the fear of
learning to code keep you away: Felix provides an exceptional foundation for learning
Python that even experienced programmers could benefit from. Moreover, he frames this
information in a way that is quickly accessible and applicable to you as an Excel user. You
can quickly tell reading this book that it was written by someone with years of experience
teaching and working with clients on how to use Excel to its fullest extent with the help of
Python programming. Felix is uniquely suited to show you the possibilities of learning
Python for Excel; I hope you enjoy the master class as much as I did.
—George Mount, Founder, Stringfest Analytics
Python is the natural progression from Excel and it’s tempting to simply discard Excel
all together. Tempting, but hardly realistic. Excel is here, and here to stay, both in the
corporate world and as a useful desktop tool at home and in the office. This book
provides the much needed bridge between these two worlds. It explains how you can
integrate Python into Excel and free yourself from the inevitable disaster of huge
workbooks, thousands of formulas, and ugly VBA hacks. Python for Excel is probably
the single most useful book on Excel that I have read and an absolute must-read for
any advanced Excel user. A highly recommended book!
—Andreas F. Clenow, CIO Acies Asset Management and author
of international best-sellers Following the Trend, Stocks on the Move,
and Trading Evolved
Excel remains a cornerstone tool of the financial world, but a vast amount of these Excel
applications are an irresponsible mess. This book does an excellent job of showing you
how to build better, more robust applications with the help of xlwings.
—Werner Brönnimann, Derivatives and DeFi practitioner
and cofounder, Ubinetic AG
Excel and Python are two of the most important tools in the Business Analytics toolbox,
and together they are far greater than the sum of their parts. In this book, Felix Zumstein
lays out his unparalleled mastery of the many ways to connect Python and Excel using
open source, cross-platform solutions. It will be an invaluable tool for business analysts
and data scientists alike, and any Python user looking to harness the
power of Excel in their code.
—Daniel Guetta, Associate Professor of Professional Practice and
Director of the Business Analytics Initiative at Columbia
Business School and coauthor of Python for MBAs
Python for Excel
A Modern Environment for Automation
and Data Analysis
Felix Zumstein
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Python for Excel, the cover image, and
related trade dress are trademarks of O’Reilly Media, Inc.
The views expressed in this work are those of the author, and do not represent the publisher’s views.
While the publisher and the author have used good faith efforts to ensure that the information and
instructions contained in this work are accurate, the publisher and the author disclaim all responsibility
for errors or omissions, including without limitation responsibility for damages resulting from the use of
or reliance on this work. Use of the information and instructions contained in this work is at your own
risk. If any code samples or other technology this work contains or describes is subject to open source
licenses or the intellectual property rights of others, it is your responsibility to ensure that your use
thereof complies with such licenses and/or rights.
978-1-492-08100-5
[LSI]
Página 1
Prefácio
Figura P-1. Lendo e gravando arquivos do Excel (Parte III) versus programação do Excel
(Parte IV)
Como Parte III não requer a instalação do Excel, tudo funciona em todas as plataformas
suportadas pelo Python, principalmente Windows, macOS e Linux. A Parte IV, no
entanto, funcionará apenas nas plataformas suportadas pelo Microsoft Excel, ou seja,
Windows e macOS, pois o código depende de uma instalação local do Microsoft Excel.
1 A Microsoft começou a usar o termo funções personalizadas em vez de UDFs. Neste livro, continuarei a chamá-los
de UDFs.
Página 5 Prefácio
Convenções usadas neste livro
As seguintes convenções tipográficas são usadas neste livro:
Itálico
Indica novos termos, URLs, endereços de e-mail, nomes de arquivo e extensões de
arquivo.
Largura constante
Usada para listagens de programas, bem como dentro de parágrafos para fazer
referência a elementos de programa, como nomes de variáveis ou funções, bancos de
dados, tipos de dados, variáveis de ambiente, instruções e palavras-chave.
Largura constante negrito
Mostra comandos ou outro texto que deve ser digitado literalmente pelo usuário.
largura constante
Mostra o texto que deve ser substituído por valores fornecidos pelo usuário ou por
valores determinados pelo contexto.
Temos uma página na web para este livro, onde listamos erratas, exemplos e qualquer
informação adicional. Você pode acessar esta página em https://oreil.ly/py4excel.
mail [email protected] para comentar ou fazer perguntas técnicas sobre este livro.
Para obter mais informações sobre nossos livros, cursos, conferências e notícias, consulte
nosso site em http://www.oreilly.com.
Encontre-nos no Facebook:
http://facebook.com/oreilly. Siga-nos no Twitter:
http://twitter.com/oreillymedia.
Assista-nos no YouTube: http://www.youtube.com/oreillymedia.
Agradecimentos
Como autor de primeira viagem, sou incrivelmente grato pela ajuda que recebi de tantas
pessoas ao longo do caminho - eles tornaram essa jornada muito mais fácil para mim!
Na O'Reilly, gostaria de agradecer à minha editora, Melissa Potter, que fez um ótimo
trabalho em me manter motivada e dentro do cronograma e que me ajudou a tornar este
livro legível. Também gostaria de agradecer a Michelle Smith, que trabalhou comigo na
proposta inicial do livro, e a Daniel Elfanbaum, que nunca se cansou de responder minhas
perguntas técnicas.
Um grande obrigado a todos os meus colegas, amigos e clientes que investiram muitas horas
na leitura das primeiras formas de meus rascunhos. O feedback deles foi crucial para tornar
o livro mais fácil de entender, e alguns dos estudos de caso são inspirados em problemas reais
do Excel que eles compartilharam comigo. Meus agradecimentos a Adam Rodriguez, Mano
Beeslar, Simon Schiegg, Rui Da Costa, Jürg Nager e Christophe de Montrichard.
Também recebi feedback útil dos leitores da versão Early Release que foi publicada na
plataforma de aprendizado on-line O'Reilly. Obrigado Felipe Maion, Ray Doue, Kolyu
Minevski, Scott Drummond, Volker Roth e David Ruggles!
Página 8 Prefácio
Tive muita sorte que o livro foi revisado por revisores técnicos altamente qualificados e eu
realmente aprecio o trabalho duro que eles colocaram sob muita pressão de tempo.
Obrigado por toda a ajuda, Jordan Goldmeier, George Mount, Andreas Clenow, Werner
Brönnimann e Eric Moreira!
Agradecimentos especiais vão para Björn Stiel, que não era apenas um revisor de tecnologia,
mas com quem também aprendi muitas das coisas sobre as quais estou escrevendo neste
livro. Eu gostei de trabalhar com você nesses últimos anos!
Por último, mas não menos importante, gostaria de estender minha gratidão a Eric
Reynolds, que fundiu seu projeto ExcelPython na base de código xlwings em 2016. Ele
também redesenhou todo o pacote do zero, tornando minha API horrível desde os
primeiros dias uma coisa de o passado. Muito obrigado!
Página 9
CAPÍTULO 1
Por que Python para Excel?
2 James Vincent, “Cientistas renomeiam genes humanos para impedir que o Microsoft Excel os interprete mal
como datas”, The Verge, 6 de agosto de 2020, https://oreil.ly/0qo-n.
3 Leo Kelion, “Excel: Por que usar a ferramenta da Microsoft causou a perda dos resultados do COVID-19”,
BBC News, 5 de outubro de 2020, https://oreil.ly/vvB6o.
4 Links da Wikipedia para o documento em uma das rodapé em seu artigo sobre o caso.
Página 12 Capítulo 1
Para evitar que sua empresa acabe no noticiário com uma história semelhante, vamos dar
uma olhada em algumas práticas recomendadas que tornam seu trabalho com o Excel muito
mais seguro.
Separação de interesses
Um dos princípios de design mais importantes na programação é a separação de interesses,
às vezes também chamada de modularidade. Isso significa que um conjunto relacionado de
funcionalidades deve ser cuidado por uma parte independente do programa para que possa
ser facilmente substituído sem afetar o restante do aplicativo. No nível mais alto, um
aplicativo geralmente é dividido nas seguintes camadas:5
• Camada de apresentação
• Camada de negócios
• Camada de dados
Para explicar essas camadas, considere um conversor de moeda simples como o mostrado na
Figura 1-1. Você encontrará o arquivo Excel currency_converter.xlsx na pasta xl do
repositório complementar.
É assim que o aplicativo funciona: digite o Valor e a Moeda nas células A4 e B4,
respectivamente, e o Excel converterá isso em dólares americanos na célula D4. Muitos
aplicativos de planilha seguem esse design e são usados pelas empresas todos os dias. Deixe-
me dividir o aplicativo em suas camadas:
Camada de apresentação
Isto é o que você vê e interage, ou seja, a interface do usuário: os valores das células A4,
B4 e D4 junto com seus rótulos formam a camada de apresentação do conversor de
moeda.
Camada de negócios
Essa camada cuida da lógica específica do aplicativo: a célula D4 define como o valor é
convertido em USD. A fórmula =A4 * VLOOKUP(B4, F4:G11, 2, FALSE) se traduz
em Valor vezes Taxa de câmbio.
5 A terminologia foi extraída do Guia de Arquitetura de Aplicativos da Microsoft, 2ª Edição, que está
disponível online.
Página 13 Capítulo 1
Camada de dados
Como o nome sugere, esta camada se encarrega de acessar os dados: a parte VLOOKUP da
célula D4 está fazendo esse trabalho.
A camada de dados acessa os dados da tabela de câmbio que inicia na célula F3 e que
funciona como o banco de dados desta pequena aplicação. Se você prestou bastante
atenção, provavelmente notou que a célula D4 aparece em todas as três camadas: esse
aplicativo simples mistura as camadas de apresentação, negócios e dados em uma única
célula.
Isso não é necessariamente um problema para este conversor de moeda simples, mas muitas
vezes, o que começa como um pequeno arquivo do Excel logo se transforma em um
aplicativo muito maior. Como essa situação pode ser melhorada? A maioria dos recursos
profissionais para desenvolvedores do Excel aconselham você a usar uma planilha separada
para cada camada, na terminologia do Excel geralmente chamada entradas, cálculos e
saídas. Muitas vezes, isso é combinado com a definição de um determinado código de cor
para cada camada, por exemplo, um fundo azul para todas as células de entrada. No
Capítulo 11, construiremos uma aplicação real baseada nestas camadas: o Excel será a
camada de apresentação, enquanto as camadas de negócios e de dados são movidas para
Python, onde é muito mais fácil estruturar seu código corretamente.
Agora que você sabe o que significa separação de interesses, vamos descobrir o que é o
princípio DRY!
Página 14 Capítulo 1
Princípio DRY
O Pragmatic Programmer de Hunt e Thomas (Pearson Education) popularizou o
princípio DRY: não se repita. Nenhum código duplicado significa menos linhas de código
e menos erros, o que facilita a manutenção do código. Se sua lógica de negócios estiver nas
fórmulas de sua célula, é praticamente impossível aplicar o princípio DRY, pois não há
mecanismo que permita reutilizá-lo em outra pasta de trabalho. Isso, infelizmente, significa
que uma maneira comum de iniciar um novo projeto do Excel é copiar a pasta de trabalho
do projeto anterior ou de um modelo.
Se você escreve VBA, a parte mais comum de código reutilizável é uma função. Uma função
dá acesso ao mesmo bloco de código de várias macros, por exemplo. Se você tiver várias
funções que usa o tempo todo, talvez queira compartilhá-las entre pastas de trabalho. O
instrumento padrão para compartilhar código VBA entre pastas de trabalho são os
suplementos, mas os suplementos VBA não possuem uma maneira robusta de distribuí-los
e atualizá-los. Embora a Microsoft tenha introduzido um repositório interno de
suplementos do Excel para resolver esse problema, isso só funciona com suplementos
baseados em JavaScript, portanto, não é uma opção para codificadores VBA. Isso significa
que ainda é muito comum usar a abordagem copiar/colar com VBA: vamos supor que você
precise de uma spline cúbica no Excel. A função de spline cúbico é uma maneira de
interpolar uma curva com base em alguns pontos dados em um sistema de coordenadas e é
frequentemente usada por traders de renda fixa para derivar uma curva de taxa de juros para
todos os vencimentos com base em algumas combinações conhecidas de vencimento/taxa
de juros. Se você pesquisar por “Cubic Spline Excel” na internet, não demorará muito até
que você tenha uma página de código VBA que faça o que você deseja. O problema com
isso é que, mais comumente, essas funções foram escritas por uma única pessoa com
provavelmente boas intenções, mas sem documentação formal ou teste. Talvez eles
funcionem para a maioria das entradas, mas e alguns casos de borda incomuns? Se você está
negociando uma carteira de renda fixa multimilionária, você quer ter algo em que sabe que
pode confiar. Pelo menos, é isso que você ouvirá de seus auditores internos quando
descobrirem de onde vem o código.
Python facilita a distribuição de código usando um gerenciador de pacotes, como veremos
na última seção deste capítulo. Antes de chegarmos lá, no entanto, vamos continuar com os
testes, um dos pilares do desenvolvimento de software sólido.
Testando
Quando você diz a um desenvolvedor do Excel para testar suas pastas de trabalho, ele
provavelmente realizará algumas verificações aleatórias: clique em um botão e veja se a
macro ainda faz o que deveria fazer ou altere algumas entradas e verifique se a saída parece
razoável . Esta é, no entanto, uma estratégia arriscada: o Excel facilita a introdução de erros
difíceis de detectar. Por exemplo, você pode substituir uma fórmula por um valor
codificado permanentemente. Ou você esquece de ajustar uma fórmula em uma coluna
oculta.
Página 15 Capítulo 1
Quando você diz a um desenvolvedor de software profissional para testar seu código, ele
escreve testes de unidade. Como o nome sugere, é um mecanismo para testar componentes
individuais do seu programa. Por exemplo, testes de unidade garantem que uma única
função de um programa funcione corretamente. A maioria das linguagens de programação
oferece uma maneira de executar testes de unidade automaticamente. A execução de testes
automatizados aumentará drasticamente a confiabilidade de sua base de código e garantirá
razoavelmente que você não quebrará nada que funcione atualmente ao editar seu código.
Se você observar a ferramenta de conversão de moeda na Figura 1-1, poderá escrever um
teste que verifique se a fórmula na célula D4 retorna corretamente USD 105 com as
seguintes entradas: 100 EUR como valor e 1,05 como taxa de câmbio EURUSD. Por que
isso ajuda? Suponha que você apague acidentalmente a célula D4 com a fórmula de
conversão e tenha que reescrevê-la: ao invés de multiplicar o valor pela taxa de câmbio, você
divide por ela - afinal, trabalhar com moedas pode ser confuso. Ao executar o teste acima,
você obterá uma falha no teste, pois 100 EUR / 1,05 não resultará mais em 105 USD, como
o teste espera. Assim, você pode detectar e corrigir a fórmula antes de entregar a planilha aos
seus usuários.
Praticamente todas as linguagens de programação tradicionais oferecem uma ou mais
estruturas de teste para escrever testes de unidade sem muito esforço - mas não o Excel.
Felizmente, o conceito de testes de unidade é bastante simples e, ao conectar o Excel com o
Python, você obtém acesso às poderosas estruturas de teste de unidade do Python. Embora
uma apresentação mais aprofundada dos testes de unidade esteja além do escopo deste livro,
convido você a dar uma olhada no meu blog post, no qual apresento o tópico com exemplos
práticos.
Os testes de unidade geralmente são configurados para serem executados automaticamente
quando você confirma seu código no sistema de controle de versão. A próxima seção explica
o que são sistemas de controle de versão e por que eles são difíceis de usar com arquivos do
Excel.
Controle de versão
Outra característica dos programadores profissionais é que eles utilizam um sistema para
versão ou controle de fonte. Um sistema de controle de versão (VCS) rastreia as alterações
em seu código-fonte ao longo do tempo, permitindo que você veja quem alterou o quê,
quando e por quê, e permite reverter para versões antigas a qualquer momento. O sistema
de controle de versão mais popular hoje em dia é o Git. Ele foi originalmente criado para
gerenciar o código-fonte do Linux e desde então conquistou o mundo da programação –
até a Microsoft adotou o Git em 2017 para gerenciar o código-fonte do Windows. No
mundo do Excel, por outro lado, o sistema de controle de versão de longe mais popular vem
na forma de uma pasta onde os arquivos são arquivados assim:
currency_converter_v1.xlsx
currency_converter_v2_2020_04_21.xlsx
currency_converter_final_edits_Bob.xlsx
currency_converter_final_final.xlsx
Página 16 Capítulo 1
Se, diferentemente deste exemplo, o O desenvolvedor do Excel segue uma certa convenção
no nome do arquivo, não há nada inerentemente errado com isso. Mas manter um histórico
de versão de seus arquivos localmente impede você de ter acesso a aspectos importantes do
controle de origem na forma de colaboração mais fácil, revisões por pares, processos de
aprovação e logs de auditoria. E se você quiser tornar suas pastas de trabalho mais seguras e
estáveis, você não vai querer perder essas coisas. Mais comumente, os programadores
profissionais usam o Git em conexão com uma plataforma baseada na Web, como GitHub,
GitLab, Bitbucket ou Azure DevOps. Essas plataformas permitem que você trabalhe com
os chamados pull requests ou merge requests. Eles permitem que os desenvolvedores
solicitem formalmente que suas alterações sejam mescladas na base de código principal.
Uma solicitação pull oferece as seguintes informações:
• Quem é o autor das alterações
• Quando as alterações foram feitas
• Qual é o propósito das alterações conforme descrito na mensagem de confirmação
• Quais são os detalhes das alterações conforme mostrado pela visualização diff, ou seja,
um exibição que destaca as alterações em verde para código novo e vermelho para
código excluído
Isso permite que um colega de trabalho ou um chefe de equipe revise as alterações e
identifique irregularidades. Muitas vezes, um par extra de olhos será capaz de detectar uma
falha ou duas ou fornecer um feedback valioso ao programador. Com todas essas vantagens,
por que os desenvolvedores do Excel preferem usar o sistema de arquivos local e sua própria
convenção de nomenclatura em vez de um sistema profissional como o Git?
• Muitos usuários do Excel simplesmente não conhecem o Git ou desistem logo no
início, pois o Git tem uma curva de aprendizado relativamente íngreme.
• O Git permite que vários usuários trabalhem em cópias locais do mesmo arquivo em
paralelo. Depois que todos eles confirmam seu trabalho, o Git geralmente pode mesclar
todas as alterações sem nenhuma intervenção manual. Isso não funciona para arquivos
do Excel: se eles estão sendo alterados em paralelo em cópias separadas, o Git não sabe
como mesclar essas alterações de volta em um único arquivo.
• Mesmo que você consiga lidar com os problemas anteriores, o Git simplesmente não
agrega tanto valor com arquivos do Excel quanto com arquivos de texto: o Git não é
capaz de mostrar alterações entre arquivos do Excel, impedindo um processo de revisão
por pares adequado.
Por causa de todos esses problemas, minha empresa criou o xltrail, um sistema de controle
de versão baseado em Git que sabe como lidar com arquivos do Excel. Ele oculta a
complexidade do Git para que os usuários de negócios se sintam à vontade para usá-lo e
também permite que você se conecte a sistemas Git externos, caso você já esteja rastreando
seus arquivos com o GitHub, por exemplo. O xltrail rastreia os diferentes componentes de
uma pasta de trabalho, incluindo fórmulas de células, intervalos nomeados, Power Queries
e código VBA, permitindo que você aproveite os benefícios clássicos do controle de versão,
incluindo revisões por pares.
Página 17 Capítulo 1
Outra opção para facilitar o controle de versão com o Excel é mover sua lógica de negócios
do Excel para arquivos Python, algo que faremos no Capítulo 10. Como os arquivos Python
são fáceis de rastrear com o Git, você terá a parte mais importante de sua ferramenta de
planilha sob controle.
Embora esta seção seja chamada de Práticas recomendadas de programação, ela está
principalmente apontando por que elas são mais difíceis de seguir com o Excel do que com
uma linguagem de programação tradicional como o Python. Antes de voltarmos nossa
atenção para o Python, gostaria de apresentar brevemente o Power Query e o Power Pivot,
a tentativa da Microsoft de modernizar o Excel.
Excel Moderno
A era moderna do Excel começou com o Excel 2007, quando o menu da faixa de opções e
os novos formatos de arquivo (por exemplo, xlsx em vez de xls) foram introduzidos. No
entanto, a comunidade do Excel usa o Excel moderno para se referir às ferramentas que
foram adicionadas ao Excel 2010: o mais importante, o Power Query e o Power Pivot. Eles
permitem que você se conecte a fontes de dados externas e analise dados grandes demais para
caber em uma planilha. Como sua funcionalidade se sobrepõe ao que faremos com os
pandas no Capítulo 5, vou apresentá-los brevemente na primeira parte desta seção. A
segunda parte é sobre o Power BI, que pode ser descrito como um aplicativo de business
intelligence autônomo que combina a funcionalidade do Power Query e do Power Pivot
com recursos de visualização — e tem suporte interno para Python!
Power Query e Power Pivot
Com o Excel 2010, a Microsoft introduziu um suplemento chamado Power Query. O
Power Query se conecta a várias fontes de dados, incluindo pastas de trabalho do Excel,
arquivos CSV e bancos de dados SQL. Ele também oferece conexões com plataformas como
o Salesforce e pode até ser estendido para conectar-se a sistemas que não são cobertos
imediatamente. A principal funcionalidade do Power Query é lidar com conjuntos de dados
grandes demais para caber em uma planilha. Depois de carregar os dados, você pode
executar etapas adicionais para limpá-los e manipulá-los para que cheguem em um formato
utilizável no Excel. Você pode, por exemplo, dividir uma coluna em duas, mesclar duas
tabelas ou filtrar e agrupar seus dados. Desde o Excel 2016, o Power Query não é mais um
suplemento, mas pode ser acessado diretamente na guia Dados da faixa de opções por meio
do botão Obter Dados. O Power Query está disponível apenas parcialmente no macOS —
no entanto, está sendo desenvolvido ativamente, portanto, deve ter suporte total em uma
versão futura do Excel.
O Power Pivot anda de mãos dadas com o Power Query: conceitualmente, é a segunda
etapa depois de adquirir e limpar seus dados com o Power Query. O Power Pivot ajuda você
a analisar e apresentar seus dados de maneira atraente diretamente no Excel. Pense nisso
como uma tabela dinâmica tradicional que, como o Power Query, pode lidar com grandes
conjuntos de dados. O Power Pivot permite definir modelos de dados formais com
relacionamentos e hierarquias e você pode adicionar colunas calculadas por meio da
linguagem de fórmula DAX.
Página 18 Capítulo 1
O Power Pivot também foi introduzido com o Excel 2010, mas continua sendo um
suplemento e até agora não está disponível no macOS.
Se você gosta de trabalhar com o Power Query e o Power Pivot e deseja criar painéis sobre
eles, vale a pena dar uma olhada no Power BI — vamos ver por quê!
Power BI
O Power BI é um aplicativo autônomo lançado em 2015. É a resposta da Microsoft a
ferramentas de business intelligence como Tableau ou Qlik. O Power BI Desktop é gratuito,
portanto, se você quiser brincar com ele, vá para a página inicial do Power BI e baixe-o —
observe, no entanto, que o Power BI Desktop está disponível apenas para Windows. O
Power BI quer entender grandes conjuntos de dados visualizando-os em painéis interativos.
Em sua essência, ele conta com a mesma funcionalidade do Power Query e do Power Pivot
que o Excel. Os planos comerciais permitem que você colabore e compartilhe painéis online,
mas eles são separados da versão para desktop. A principal razão pela qual o Power BI é
interessante no contexto deste livro é que ele oferece suporte a scripts Python desde 2018.
O Python pode ser usado para a parte de consulta, bem como a parte de visualização, usando
as bibliotecas de plotagem do Python. Para mim, usar o Python no Power BI parece um
pouco desajeitado, mas a parte importante aqui é que a Microsoft reconheceu a importância
do Python em relação à análise de dados. Assim, as esperanças são altas de que um dia o
Python também encontre um caminho oficial para o Excel.
Então, o que há de tão bom no Python que chegou ao Power BI da Microsoft? A próxima
seção tem algumas respostas!
6 Você pode saber mais sobre como o Instagram usa o Python em seu blog de engenharia.
Página 19 Capítulo 1
A vantagem exclusiva do Python é que a parte com a lógica de negócios provavelmente não
precisa ser reescrita, mas pode ser movida como está do protótipo do Excel para o ambiente
da Web de produção.
Nesta seção, apresentarei os principais conceitos do Python e os compararei com o Excel e
o VBA. Abordarei a legibilidade do código, a biblioteca padrão do Python e o gerenciador
de pacotes, a pilha de computação científica, recursos de linguagem moderna e
compatibilidade entre plataformas. Vamos mergulhar na legibilidade primeiro!
Legibilidade e Manutenibilidade
Se o seu código for legível, significa que é fácil de seguir e entender — especialmente para
pessoas de fora que não escreveram o código. Isso torna mais fácil identificar erros e manter
o código daqui para frente. É por isso que uma linha em The Zen of Python é “contagens
de legibilidade”. O Zen of Python é um resumo conciso dos princípios básicos de design do
Python, e aprenderemos como imprimi-lo no próximo capítulo. Vamos dar uma olhada no
seguinte trecho de código no VBA:
If i < 5 Then
Debug.Print "i is smaller than 5"
ElseIf i <= 10 Then
Debug.Print "i is between 5 and 10"
Else
Debug.Print "i is bigger than 10"
End If
No VBA, você pode reformatar o trecho para o seguinte, que é completamente equivalente:
If i < 5 Then
Debug.Print "i is smaller than 5"
ElseIf i <= 10 Then
Debug.Print "i is between 5 and 10"
Else
Debug.Print "i is bigger than 10"
End If
Na primeira versão, o recuo visual se alinha com a lógica do código. Isso facilita a leitura e a
compreensão do código, o que novamente facilita a identificação de erros. Na segunda
versão, um desenvolvedor que é novo no código pode não ver a condição ElseIf e Else
ao examiná-la pela primeira vez - isso é obviamente ainda mais verdadeiro se o código fizer
parte de uma base de código maior.
O Python não aceita código formatado como o segundo exemplo: ele força você a alinhar o
recuo visual com a lógica do código, evitando problemas de legibilidade. O Python pode
fazer isso porque depende da indentação para definir blocos de código à medida que você
os usa em instruções if ou loops for. Em vez de recuo, a maioria das outras linguagens
usa chaves, e o VBA usa palavras-chave como End If, como acabamos de ver nos trechos
de código.
Página 20 Capítulo 1
A razão por trás do uso de recuo para blocos de código é que, na programação, a maior parte
do tempo é gasto na manutenção do código, em vez de escrevê-lo em primeiro lugar. Ter
código legível ajuda os novos programadores (ou você mesmo alguns meses depois de
escrever o código) a voltar e entender o que está acontecendo.
Aprenderemos tudo sobre as regras de recuo do Python no Capítulo 3, mas por enquanto
vamos continuar com a biblioteca padrão: a funcionalidade que vem com o Python pronto
para uso.
Por exemplo, para facilitar a busca de dados de fontes na Internet, você pode instalar o
pacote Requests para obter acesso a um conjunto de comandos que são poderosos e fáceis
de usar. Para instalá-lo, você usaria o gerenciador de pacotes do Python pip, que você
executa em um Prompt de Comando ou Terminal. pip é um acrônimo recursivo para pip
instala pacotes. Não se preocupe se isso soa um pouco abstrato agora; Vou explicar como
isso funciona em detalhes no próximo capítulo. Por enquanto, é mais importante entender
por que os gerenciadores de pacotes são tão importantes. Uma das principais razões é que
qualquer pacote razoável não dependerá apenas da biblioteca padrão do Python, mas
também de outros pacotes de código aberto que também estão hospedados no PyPI. Essas
dependências podem novamente depender de subdependências e assim por diante. pip
verifica recursivamente as dependências e subdependências de um pacote e as baixa e instala.
O pip também facilita a atualização de seus pacotes para que você possa manter suas
dependências atualizadas. Isso facilita muito a adesão ao princípio DRY, pois você não
precisa reinventar ou copiar/colar o que já está disponível no PyPI.
Página 21 Capítulo 1
Com pip e PyPI, você também tem um mecanismo sólido para distribuir e instalar essas
dependências, algo que o Excel não tem com seus add-ins tradicionais.
Com o pip, você pode instalar pacotes para praticamente qualquer coisa, mas para usuários
do Excel, alguns dos mais interessantes certamente são os pacotes para computação
científica. Vamos aprender um pouco mais sobre computação científica com Python na
próxima seção!
Computação Científica
Uma razão importante para o sucesso do Python é o fato de ele ter sido criado como uma
linguagem de programação de uso geral. Os recursos para computação científica foram
adicionados posteriormente na forma de pacotes de terceiros. Isso tem a vantagem única de
que um cientista de dados pode usar a mesma linguagem para experimentos e pesquisas que
um desenvolvedor da Web, que pode eventualmente criar um aplicativo pronto para
produção em torno do núcleo computacional. Ser capaz de construir aplicativos científicos
a partir de uma linguagem reduz o atrito, o tempo de implementação e os custos. Pacotes
científicos como NumPy, SciPy e pandas nos dão acesso a uma maneira muito concisa de
formular problemas matemáticos. Como exemplo, vamos dar uma olhada em uma das
fórmulas financeiras mais famosas usadas para calcular a variância do portfólio de acordo
com a Teoria Moderna do Portfólio:
σ2 = wTCw
Página 22 Capítulo 1
A variação da carteira é indicada por σ2, enquanti w é o vetor de peso dos ativos individuais
e C é a matriz de covariância da carteira. Se w e C são intervalos do Excel, você pode calcular
a variação do portfólio no VBA assim:
variance = Application.MMult(Application.MMult(Application.Transpose(w), C), w)
Compare isso com a notação quase matemática em Python, supondo que w e C são pandas
DataFrames ou matrizes NumPy (vou apresentá-los formalmente na Parte II):
variance = w.T @ C @ w
Mas não se trata apenas de estética e legibilidade: NumPy e pandas usam código Fortran e
C compilado sob o capô, o que aumenta o desempenho ao trabalhar com grandes matrizes
em comparação com o VBA.
A falta de suporte para computação científica é uma limitação óbvia no VBA. Mas mesmo
se você olhar para os principais recursos da linguagem, o VBA ficou para trás, como
mostrarei na próxima seção.
Sempre que você tem um CreateObject chamada ou está sendo instruído a ir para
Ferramentas > Referências no editor VBA para adicionar uma referência, você está quase
sempre lidando com código que será executado apenas no Windows. Outra área
proeminente em que você precisa observar se deseja que seus arquivos do Excel funcionem
no Windows e no macOS são ActiveX controls. ActiveX controls são elementos como
botões e listas suspensas que você pode colocar em suas planilhas, mas funcionam apenas
no Windows. Certifique-se de evitá-los se quiser que sua pasta de trabalho também seja
executada no macOS!
Página 24 Capítulo 1
Conclusão
Neste capítulo, conhecemos Python e Excel, duas tecnologias muito populares que existem
há várias décadas — muito tempo em comparação com muitas outras tecnologias que
usamos hoje.
A London Whale serviu como um exemplo de quanto pode dar errado (em dólares) quando
você não usa o Excel corretamente com pastas de trabalho de missão crítica. Essa foi nossa
motivação para analisar um conjunto mínimo de práticas recomendadas de programação:
aplicar a separação de interesses, seguir o princípio DRY e fazer uso de testes automatizados
e controle de versão. Em seguida, analisamos o Power Query e o Power Pivot, a abordagem
da Microsoft para lidar com dados maiores que sua planilha. Eu, no entanto, sinto que eles
geralmente não são a solução certa, pois prendem você no mundo da Microsoft e impedem
que você aproveite a flexibilidade e o poder das soluções modernas baseadas em nuvem.
O Python vem com recursos convincentes que estão faltando no Excel: a biblioteca padrão,
o gerenciador de pacotes, bibliotecas para computação científica e compatibilidade entre
plataformas. Ao aprender a combinar o Excel com o Python, você pode ter o melhor dos
dois mundos e economizar tempo com a automação, cometer menos erros, pois é mais fácil
seguir as práticas recomendadas de programação e poderá levar seu aplicativo e escalá-lo para
fora do Excel se você precisar.
Agora que você sabe por que o Python é um companheiro tão poderoso para o Excel, é hora
de configurar seu ambiente de desenvolvimento para poder escrever suas primeiras linhas
de código Python!
Página 25
CAPÍTULO 2
Ambiente de desenvolvimento
Você provavelmente não pode esperar para aprender o básico do Python, mas antes de
chegarmos lá, primeiro você precisa configurar seu computador de acordo. Para escrever
código VBA ou Power Queries, basta iniciar o Excel e abrir o editor VBA ou Power Query,
respectivamente. Com Python, é um pouco mais trabalhoso.
Começaremos este capítulo instalando a distribuição Anaconda Python. Além de instalar o
Python, o Anaconda também nos dará acesso ao Anaconda Prompt e aos notebooks
Jupyter, duas ferramentas essenciais que usaremos ao longo deste livro. O Prompt do
Anaconda é um Prompt de Comando especial (Windows) ou Terminal (macOS); ele nos
permite executar scripts Python e outras ferramentas de linha de comando que veremos
neste livro. Os notebooks Jupyter nos permitem trabalhar com dados, código e gráficos de
maneira interativa, o que os torna um sério concorrente das pastas de trabalho do Excel.
Depois de brincar com os notebooks Jupyter, instalaremos o Visual Studio Code (VS
Code), um poderoso editor de texto. O VS Code funciona muito bem para escrever,
executar e depurar scripts Python e vem com um Terminal integrado. Figura 2-1 resume o
que está incluído no Anaconda e no VS Code.
Como este livro é sobre o Excel, estou me concentrando no Windows e no macOS neste
capítulo. No entanto, tudo até e incluindo a Parte III também é executado no Linux. Vamos
começar instalando o Anaconda!
Página 26 Capítulo 2
Instalação
Vá para a página inicial do Anaconda e baixe a versão mais recente do instalador do
Anaconda (Edição Individual). Certifique-se de baixar o instalador gráfico de 64 bits para a
versão Python 3.x.1 Após o download, clique duas vezes no instalador para iniciar o processo
de instalação e certifique-se de aceitar todos os padrões. Para instruções de instalação mais
detalhadas, siga a documentação oficial.
1 Sistemas de 32 bits só existem com Windows e se tornaram raros. Uma maneira fácil de descobrir qual versão do
Windows você possui é acessar a C:\ no Explorador de Arquivos. Se você puder ver as pastas Arquivos e
Programas (x86), você está em uma versão de 64 bits do Windows. Se você puder ver apenas a pasta Arquivos de
Programas, você está em um sistema de 32 bits.
Página 27 Capítulo 2
Outras distribuições do Python
Embora as instruções neste livro suponham que você tenha o Anaconda Individual
Edition instalado, o código e os conceitos mostrados também funcionarão com
qualquer outra instalação do Python. Nesse caso, no entanto, você terá que instalar
as dependências necessárias seguindo as instruções incluídas em requirements.txt
no repositório complementar.
Com o Anaconda instalado, agora podemos começar a usar o Prompt do Anaconda. Vamos
ver o que é isso e como funciona!
Prompt do Anaconda
O Anaconda é realmente apenas um Prompt de Comando no Windows e um Terminal no
macOS que foi configurado para ser executado com o interpretador Python correto e
pacotes de terceiros. O Anaconda Prompt é a ferramenta mais básica para executar código
Python, e faremos uso extensivo dele neste livro para executar scripts Python e todos os tipos
de ferramentas de linha de comando oferecidas por vários pacotes.
Se você estiver em uma versão mais antiga do macOS, será mais ou menos assim :
(base) MacBook-Pro:~ felix$
Extensões de arquivo
Infelizmente, o Windows e o macOS ocultam as extensões de arquivo por padrão no
Windows Explorer ou no macOS Finder, respectivamente. Isso pode dificultar o trabalho
com scripts Python e o Prompt do Anaconda, pois eles exigirão que você consulte os
arquivos, incluindo suas extensões.
Página 30 Capítulo 2
Ao trabalhar com o Excel, mostrar as extensões de arquivo também ajuda a entender se você
está lidando com o arquivo padrão xlsx, um arquivo habilitado para macro xlsm ou
qualquer outro formato de arquivo do Excel. Aqui está como você torna as extensões de
arquivo visíveis:
Windows
Abra um Explorador de Arquivos e clique na guia Exibir. No grupo Mostrar/Ocultar,
ative a caixa de seleção "Extensões de nome de arquivo".
macOS
Abra o Finder e vá para Preferências pressionando Command-, (Command-comma). Na
guia Avançado, marque a caixa ao lado de "Mostrar todas as extensões de nome de
arquivo".
O texto que é impresso em um Terminal no macOS será um pouco diferente, mas, por
outro lado, funciona da mesma forma. Este livro é baseado no Python 3.8 — se você quiser
usar uma versão mais recente do Python, certifique-se de consultar a página inicial do livro
para instruções.
Essa sessão interativa do Python também é chamada de Python REPL, que significa read-
eval-print loop: Python lê sua entrada, avalia-a e imprime o resultado instantaneamente
enquanto aguarda sua próxima entrada. Lembra do Zen do Python que mencionei no
capítulo anterior? Agora você pode ler a versão completa para obter algumas informações
sobre os princípios orientadores do Python (sorriso incluído). Simplesmente execute esta
linha pressionando Enter depois de digitá-la:
>>> import this
Para sair da sua sessão Python, digite quit() seguido da tecla Enter. Como alternativa,
pressione Ctrl + Z no Windows e, em seguida, pressione a tecla Enter. No macOS, basta
pressionar Ctrl-D - não é necessário pressionar Enter.
Tendo saído do Python REPL, é um bom momento para brincar com Conda e pip, os
gerenciadores de pacotes que vêm com a instalação do Anaconda.
Por exemplo, para ver quais pacotes já estão disponíveis em sua distribuição Anaconda,
digite:
(base)> conda list
Sempre que este livro requer um pacote que não está incluído na instalação do Anaconda,
vou apontar isso explicitamente e mostrar como instalá-lo. No entanto, pode ser uma boa
ideia cuidar da instalação dos pacotes ausentes agora, para que você não precise lidar com
isso mais tarde. Vamos primeiro instalar plotly e xlutils, os pacotes que estão disponíveis via
Conda:
(base)> conda install plotly xlutils
Depois de executar este comando, o Conda mostrará o que ele fará e exigirá que você
confirme digitando y e pressionando Enter. Uma vez feito, você pode instalar pyxlsb e
pytrends com pip, pois esses pacotes não estão disponíveis via Conda:
(base)> pip install pyxlsb pytrends
Versões de Pacotes
Muitos pacotes Python são atualizados com frequência e às vezes introduzem
alterações que não são compatíveis com versões anteriores. Isso provavelmente
quebrará alguns dos exemplos deste livro. Vou tentar acompanhar essas mudanças
e postar correções na página inicial do livro, mas você também pode criar um
ambiente Conda que use as mesmas versões dos pacotes que eu estava usando ao
escrever este livro. Apresentarei os ambientes Conda na próxima seção e você
encontrará instruções detalhadas sobre como criar um ambiente Conda com os
pacotes específicos no Apêndice A.
Página 33 Capítulo 2
Agora você sabe como usar o Prompt do Anaconda para iniciar um interpretador Python e
instalar pacotes adicionais. Na próxima seção, explicarei o que (base) no início do Prompt
do Anaconda.
Ambientes Conda
Você pode estar se perguntando por que o Prompt do Anaconda é exibido (base) no início
de cada linha de entrada. É o nome do ambiente Conda. Um ambiente Conda é um
“mundo Python” separado com uma versão específica do Python e um conjunto de pacotes
instalados com versões específicas. Por que isso é necessário? Quando você começa a
trabalhar em diferentes projetos em paralelo, eles terão requisitos diferentes: um projeto
pode usar Python 3.8 com pandas 0.25.0, enquanto outro projeto pode usar Python
3.9 com pandas 1.0.0. O código escrito para o pandas 0.25.0 geralmente exigirá alterações
para executar com o pandas 1.0.0, portanto, você não pode simplesmente atualizar suas
versões do Python e do pandas sem fazer alterações em seu código. Usar um ambiente
Conda para cada projeto garante que cada projeto seja executado com as dependências
corretas. Embora os ambientes Conda sejam específicos da distribuição Anaconda, o
conceito existe em todas as instalações do Python sob o nome de ambiente virtual. Os
ambientes Conda são mais poderosos porque facilitam lidar com diferentes versões do
próprio Python, não apenas com pacotes.
Enquanto você trabalha com este livro, você não terá que mudar seu ambiente Conda, pois
sempre estaremos usando o base . No entanto, quando você começa a construir projetos
reais, é uma boa prática usar um ambiente Conda ou virtual para cada projeto para evitar
possíveis conflitos entre suas dependências. Tudo o que você precisa saber sobre como lidar
com vários ambientes Conda é explicado no Apêndice A. Lá você também encontrará
instruções sobre como criar um ambiente Conda com as versões exatas dos pacotes que usei
para escrever este livro. Isso permitirá que você execute os exemplos neste livro como estão
por muitos anos. A outra opção é assistir a página inicial do livro para possíveis alterações
necessárias para versões mais recentes do Python e dos pacotes.
Tendo resolvido o mistério em torno dos ambientes Conda, é hora de apresentar a próxima
ferramenta, que usaremos intensamente neste livro: notebooks Jupyter!
Notebooks Jupyter
Na seção anterior, mostrei como iniciar uma sessão interativa do Python a partir de um
prompt do Anaconda. Isso é útil se você quiser um ambiente básico para testar algo simples.
Para a maior parte do seu trabalho, no entanto, você deseja um ambiente que seja mais fácil
de usar. Por exemplo, voltar aos comandos anteriores e exibir gráficos é difícil com um
Python REPL rodando em um Anaconda Prompt. Felizmente, o Anaconda vem com
muito mais do que apenas o interpretador Python: ele também inclui notebooks Jupyter,
que surgiram como uma das maneiras mais populares de executar código Python em um
contexto de ciência de dados.
Página 34 Capítulo 2
Os notebooks Jupyter permitem que você conte uma história combinando código Python
executável com texto, imagens e gráficos formatados em um notebook interativo que é
executado em seu navegador. Eles são amigáveis para iniciantes e, portanto, especialmente
úteis para os primeiros passos de sua jornada em Python. Eles são, no entanto, também
muito populares para ensino, prototipagem e pesquisa, pois facilitam a pesquisa
reproduzível.
Os notebooks Jupyter tornaram-se um concorrente sério do Excel, pois cobrem
aproximadamente o mesmo caso de uso de uma pasta de trabalho: você pode preparar,
analisar e visualizar dados rapidamente. A diferença para o Excel é que tudo isso acontece
escrevendo código Python em vez de clicar no Excel com o mouse. Outra vantagem é que
os notebooks Jupyter não misturam dados e lógica de negócios: o notebook Jupyter contém
seu código e gráficos, enquanto você normalmente consome dados de um arquivo CSV
externo ou de um banco de dados. Ter o código Python visível em seu notebook facilita ver
o que está acontecendo em comparação com o Excel, onde as fórmulas estão escondidas
atrás do valor de uma célula. Os notebooks Jupyter também são fáceis de executar
localmente e em um servidor remoto. Os servidores geralmente têm mais poder do que sua
máquina local e podem executar seu código totalmente sem supervisão, algo difícil de fazer
com o Excel.
Nesta seção, mostrarei o básico de como você executa e navega em um notebook Jupyter:
aprenderemos sobre células de notebook e veremos qual é a diferença entre o modo de
edição e de comando. Em seguida, entenderemos por que a ordem de execução das células
é importante antes de encerrar esta seção, aprendendo como desligar notebooks
corretamente. Vamos começar com nosso primeiro caderno!
Isso será automaticamente abra seu navegador e mostre o painel do Jupyter com os arquivos
no diretório de onde você estava executando o comando. No canto superior direito do
painel do Jupyter, clique em Novo e selecione Python 3 na lista suspensa (veja a Figura 2-2).
Página 35 Capítulo 2
Isso abrirá uma nova guia do navegador com seu primeiro notebook Jupyter vazio,
conforme mostrado na Figura 2-3.
É um bom hábito clicar em Untitled1 ao lado do logotipo do Jupyter para renomear sua
pasta de trabalho para algo mais significativo, por exemplo, first_notebook. A parte inferior
da Figura 2-3 mostra uma célula de notebook — vá para a próxima seção para saber mais
sobre eles!
Células do Notebook
Na Figura 2-3, você vê uma célula vazia com um cursor piscando. Se o cursor não piscar,
clique na célula com o mouse, ou seja, à direita de In [ ]. Agora repita o exercício da última
seção: digite 3 + 4 e execute a célula clicando no botão Executar na barra de menu na parte
superior ou - muito mais fácil - pressionando Shift + Enter. Isso executará o código na célula,
imprimirá o resultado abaixo da célula e pulará para a próxima célula. Nesse caso, ele insere
uma célula vazia abaixo, pois temos apenas uma célula até o momento.
Página 36 Capítulo 2
Entrando um pouco mais em detalhes: enquanto uma célula está calculando, ela mostra In
[*] e quando termina, o asterisco se transforma em um número, por exemplo, In [1].
Abaixo da célula você terá a saída correspondente rotulada com o mesmo número: Out [1].
Toda vez que você executa uma célula, o contador aumenta em um, o que ajuda você a ver
em qual ordem as células foram executadas. Daqui para frente, mostrarei os exemplos de
código neste formato, por exemplo, o exemplo REPL de antes se parece com isso:
In [1]: 3 + 4
Out[1]: 7
Esta notação permite que você acompanhe facilmente digitando 3 + 4 em uma célula de
notebook. Ao executá-lo pressionando Shift+Enter, você obterá o que mostro como saída
em Out[1]. Se você ler este livro em um formato eletrônico que suporte cores, notará que a
célula de entrada formata strings, números e assim por diante com cores diferentes para
facilitar a leitura. Isso é chamado de realce de sintaxe.
Saída da célula
Se a última linha em uma célula retornar um valor, ela será impressa
automaticamente pelo notebook Jupyter em Out [ ].entanto, quando você usa a
função print ou quando você recebe uma exceção, ela é impressa diretamente
abaixo da célula sem In o rótulo Out [ ]. Os exemplos de código neste livro são
formatados para refletir esse comportamento.
As células podem ter diferentes tipos, dois dos quais nos interessam:
Código
Este é o tipo padrão. Use-o sempre que quiser executar o código Python.
Markdown
Markdown é uma sintaxe que usa caracteres de texto padrão para formatação e pode
ser usada para incluir explicações e instruções bem formatadas em seu notebook.
Para alterar o tipo de célula para Markdown, selecione a célula e escolha Markdown no
menu suspenso do modo de célula (consulte a Figura 2-3). Mostrarei um atalho de teclado
para alterar o modo de célula na Tabela 2-3. Depois de transformar uma célula vazia em uma
célula Markdown, digite o seguinte texto, que explica algumas regras de Markdown:
# Este é um título de primeiro nível
Figura 2-4. O notebook depois de executar uma célula de código e uma célula Markdown
Agora que você conhece o código e os tipos de célula Markdown, é hora de aprender uma
maneira mais fácil de navegar entre as células: a próxima seção apresenta o modo de edição
e comando junto com alguns atalhos de teclado .
Conhecer esses atalhos de teclado permitirá que você trabalhe com blocos de anotações de
maneira eficiente sem precisar alternar entre o teclado e o mouse o tempo todo. Na próxima
seção, mostrarei uma pegadinha comum que você precisa estar ciente ao usar notebooks
Jupyter: a importância de executar células em ordem.
Cell Out[3] imprime o valor 1 como esperado. No entanto, se você agora voltar e executar
In[3] novamente, você terminará nesta situação:
In [2]: a = 1
In [5]: a
Out[5]: 2
In [4]: a = 2
Out[5] mostra agora o valor 2, que provavelmente não é o que você esperaria ao ler o
notebook de cima para baixo, especialmente se a célula In[4] estiver mais distante, exigindo
que você role para baixo. Para evitar esses casos, recomendo que você execute novamente
não apenas uma única célula, mas todas as células anteriores também. Os notebooks Jupyter
oferecem uma maneira fácil de fazer isso no menu Célula > Executar tudo acima. Depois
dessas palavras de cautela, vamos ver como você desliga um notebook corretamente!
Página 39 Capítulo 2
Desligando Notebooks Jupyter
Cada notebook é executado em um kernel Jupyter. Um kernel é o “motor” que executa o
código Python que você digita em uma célula do notebook. Cada kernel usa recursos do seu
sistema operacional na forma de CPU e RAM. Portanto, quando você fecha um notebook,
você também deve desligar seu kernel para que os recursos possam ser usados novamente
por outras tarefas - isso evitará que seu sistema fique lento. A maneira mais fácil de fazer isso
é fechando um notebook via Arquivo > Fechar e Parar. Se você simplesmente fechar a guia
do navegador, o kernel não será desligado automaticamente. Como alternativa, no painel
Jupyter, você pode fechar os notebooks em execução na guia Em execução.
Para encerrar todo o servidor Jupyter, clique no botão Sair no canto superior direito do
painel Jupyter. Se você já fechou seu navegador, você pode digitar Ctrl+C duas vezes no
Prompt do Anaconda onde o servidor do notebook está sendo executado ou fechar o
Prompt do Anaconda completamente.
Para descobrir se você concorda com meus elogios ao VS Code, não há melhor maneira do
que instalá-lo e experimentá-lo. A próxima seção é para você começar!
Página 42 Capítulo 2
Instalação e configuração
Baixe o instalador da página inicial do VS Code. Para obter as instruções de instalação mais
recentes, consulte sempre os documentos oficiais.
Windows
Clique duas vezes no instalador e aceite todos os padrões. Em seguida, abra o VS Code
no menu Iniciar do Windows, onde você o encontrará no Visual Studio Code.
macOS
Clique duas vezes no arquivo ZIP para descompactar o aplicativo. Em seguida, arraste
e solte o Visual Studio Code.app na Aplicativos : agora você pode iniciá-lo a partir do
Launchpad. Se o aplicativo não iniciar, vá para Preferências do Sistema > Segurança e
Privacidade > Geral e escolha Abrir Mesmo assim.
Quando você abre o VS Code pela primeira vez, ele se parece com a Figura 2-5. Observe que
mudei do tema escuro padrão para um tema claro para facilitar a leitura das capturas de tela.
Iniciar o VS Code dessa maneira fará com que o Explorer na barra de atividades mostre
automaticamente o conteúdo do diretório em que você estava quando executou o comando
code.
Como alternativa, você também pode abrir um diretório via Arquivo > Abrir pasta (no
macOS: Arquivo > Abrir), mas isso pode causar erros de permissão no macOS quando
começamos a usar xlwings na Parte IV. Ao passar o mouse sobre a lista de arquivos no
Explorer na barra de atividades, você verá o botão New File aparecer como mostrado na
Figura 2-6. Clique em New File e chame seu arquivo hello_world.py, depois pressione Enter.
Uma vez aberto no editor, escreva a seguinte linha de código:
print("hello world!")
Neste ponto, você sabe como criar, editar e executar scripts Python no VS Code. No
entanto, o VS Code pode fazer um pouco mais: no Apêndice B, explico como usar o
depurador e como você pode executar notebooks Jupyter com o VS Code.
Conclusão
Neste capítulo, mostrei como instalar e usar as ferramentas com as quais trabalharemos: o
Anaconda Prompt, notebooks Jupyter e VS Code. Também executamos um pouco de
código Python em um Python REPL, em um notebook Jupyter e como script no VS Code.
Eu recomendo que você se sinta confortável com o Anaconda Prompt, pois ele lhe dará
muito poder quando você se acostumar. A capacidade de trabalhar com notebooks Jupyter
na nuvem também é muito confortável, pois permite executar as amostras de código das três
primeiras partes deste livro em seu navegador.
Com um ambiente de desenvolvimento funcional, agora você está pronto para abordar o
próximo capítulo, onde aprenderá Python o suficiente para poder seguir o restante do livro.
Página 48
CAPÍTULO 3
Introdução ao Python
Com o Anaconda instalado e os notebooks Jupyter funcionando, você tem tudo pronto
para começar com o Python. Embora este capítulo não vá muito além do básico, ele ainda
cobre muito terreno. Se você está no início de sua carreira de codificação, pode haver muito
o que digerir. No entanto, a maioria dos conceitos ficará mais clara quando você usá-los em
capítulos posteriores como parte de um exemplo prático, portanto, não há necessidade de
se preocupar se você não entender algo completamente na primeira vez. Sempre que Python
e VBA diferem significativamente, vou apontar isso para garantir que você possa fazer a
transição do VBA para o Python sem problemas e esteja ciente das armadilhas óbvias. Se
você não fez nenhum VBA antes, sinta-se à vontade para ignorar essas partes.
Vou começar este capítulo com os tipos de dados básicos do Python, como inteiros e strings.
Depois disso, apresentarei indexação e fatiamento, um conceito central em Python que dá
acesso a elementos específicos de uma sequência. A seguir estão as estruturas de dados como
listas e dicionários que podem conter vários objetos. Continuarei com a instrução if e os
loops for e while antes de chegar à introdução de funções e módulos que permitem
organizar e estruturar seu código. Para encerrar este capítulo, mostrarei como formatar seu
código Python corretamente. Como você provavelmente já deve ter adivinhado, este
capítulo é o mais técnico possível. Executar os exemplos você mesmo em um notebook
Jupyter é, portanto, uma boa ideia para tornar tudo um pouco mais interativo e divertido.
Digite os exemplos você mesmo ou execute-os usando os blocos de anotações fornecidos no
repositório complementar.
Tipos de dados
Python, como qualquer outra linguagem de programação, trata números, texto, booleanos,
etc. de forma diferente, atribuindo-lhes um tipo de dados. Os tipos de dados que usaremos
com mais frequência são inteiros, flutuantes, booleanos e strings.
Página 49 Capítulo 3
Nesta seção, vou apresentá-los um após o outro com alguns exemplos. Para poder entender
os tipos de dados, porém, primeiro preciso explicar o que é um objeto.
Objetos
Em Python, tudo é um objeto, incluindo números, strings, funções e tudo mais que
veremos neste capítulo. Objetos podem tornar coisas complexas fáceis e intuitivas, dando
acesso a um conjunto de variáveis e funções. Então, antes de mais nada, deixe-me dizer
algumas palavras sobre variáveis e funções!
Variáveis
Em Python, uma variável é um nome que você atribui a um objeto usando o sinal de igual.
Na primeira linha do exemplo a seguir, o nome a é atribuído ao objeto 3:
In [1]: a = 3
b = 4
a + b
Out[1]: 7
Isso funciona da mesma forma para todos os objetos, o que é mais simples comparado ao
VBA, onde você usa o sinal de igual para tipos de dados como números e strings e a instrução
Set para objetos como pastas de trabalho ou planilhas. Em Python, você altera o tipo de
uma variável simplesmente atribuindo-a a um novo objeto. Isso é chamado de tipagem
dinâmica:
Em [2]: a = 3
print(a)
a = "three"
print(a)
3
three
Após esta breve introdução às variáveis, vamos ver como podemos fazer chamadas de função!
Funções
Apresentarei funções com muito mais detalhes posteriormente neste capítulo. Por
enquanto, você deve simplesmente saber como chamar funções internas como print que
usamos no exemplo de código anterior. Para chamar uma função, você adiciona parênteses
ao nome da função e fornece os argumentos dentro dos parênteses, o que é praticamente
equivalente à notação matemática:
Página 50 Capítulo 3
function_name(argument1, argument2, ...)
Vamos agora ver como as variáveis e funções funcionam no contexto dos objetos!
Atributos e métodos
No contexto de objetos, as variáveis são chamadas de atributos e as funções são chamadas
de métodos: os atributos dão acesso aos dados de um objeto e os métodos permitem que
você execute uma ação. Para acessar atributos e métodos, você usa a notação de ponto assim:
myobject.attribute e myobject.method().
Vamos tornar isso um pouco mais tangível: se você escreve um jogo de corrida de carros,
provavelmente usaria um objeto que representa um carro. O objeto car pode ter um
atributo speed que permite obter a velocidade atual via car.speed, e você pode acelerar o
carro chamando o método de aceleração car.accelerate(10), o que aumentaria a
velocidade em dez milhas por hora.
O tipo de um objeto e com isso seu comportamento é definido por uma classe, então o
exemplo anterior exigiria que você escrevesse uma classe Car. O processo de obter um
objeto car de uma classe Car é chamado instanciação e você instancia um objeto
chamando a classe da mesma forma que chama uma função: car = Car(). Não vamos
escrever nossas próprias classes neste livro, mas se você estiver interessado em como isso
funciona, dê uma olhada no Apêndice C.
Usaremos um primeiro método de objeto na próxima seção para tornar uma string de texto
em maiúscula, e voltaremos ao tópico de objetos e classes quando falarmos sobre objetos
datetime no final deste capítulo. Agora, no entanto, vamos continuar com os objetos que
têm um tipo de dados numérico!
Tipos Numéricos
Os tipos de dados int e float representam números inteiros e de ponto flutuante,
respectivamente. Para descobrir o tipo de dado de um determinado objeto, use o type:
In [3]: type(4)
Out[3]: int
In [4]: type(4.4)
Out[4]: float
Se você quiser forçar um número a ser um float em vez de um int, é bom o suficiente usar
um ponto decimal à direita ou o construtor float:
In [5]: type(4.)
Out[5]: float
In [6]: float(4)
Página 51 Capítulo 3
Out[6]: 4.0
O último exemplo também pode ser invertido: usando o construtor int, você pode
transformar um float em um int. Se a parte fracionária não for zero, ela será truncada:
In [7]: int(4.9)
Out[7]: 4
Python tem mais alguns tipos numéricos que não usarei ou discutirei neste livro: existem os
tipos de dados decimal, fraction e complex. Se imprecisões de ponto flutuante forem
um problema (consulte a barra lateral), use o tipo decimal para obter resultados exatos.
Esses casos são muito raros, no entanto. Como regra geral: se o Excel for bom o suficiente
para os cálculos, use floats.
Operadores matemáticos
Calcular com números requer o uso de operadores matemáticos como mais ou Sinal de
menos. Exceto pelo operador de potência, não deve haver nenhuma surpresa se você vier do
Excel:
In [9]: 3 + 4 # Sum
Out[9]: 7
Página 52 Capítulo 3
In [10]: 3 - 4 # Subtraction
Out[10]: -1
In [11]: 3 / 4 # Division
Out[11]: 0.75
In [12]: 3 * 4 # Multiplication
Out[12]: 12
In [13]: 3**4 # The power operator (Excel uses 3^4)
Out[13]: 81
In [14]: 3 * (3 + 4) # Use of parentheses
Out[14]: 21
Comentários
Nos exemplos anteriores, eu estava descrevendo a operação do exemplo usando
comentários ( ex., # Sum). Os comentários ajudam outras pessoas (e você mesmo algumas
semanas depois de escrever o código) a entender o que está acontecendo em seu programa.
É uma boa prática comentar apenas as coisas que ainda não são evidentes na leitura do
código: na dúvida, é melhor não comentar do que um comentário desatualizado que
contradiz o código. Qualquer coisa que comece com um sinal de hash é um comentário em
Python e é ignorado quando você executa o código:
In [15]: # Este é um exemplo que vimos antes.
# Cada linha de comentário deve começar com #
3 + 4
Out[15]: 7
In [16]: 3 + 4 # Este é um comentário embutido
Out[16]: 7
Tendo cobertos inteiros e floats, vamos direto para a próxima seção sobre booleanos!
Booleans
Os tipos boolean em Python são True ou False, exatamente como no VBA. Os operadores
booleanos and, or, e not, no entanto, são todos minúsculos, enquanto o VBA os mostra em
maiúsculas.
Página 53 Capítulo 3
As expressões booleanas são semelhantes à forma como funcionam no Excel, exceto pelos
operadores de igualdade e desigualdade:
In [17]: 3 == 4 # Equality (Excel usa 3 = 4)
Out[17]: False
In [18]: 3 != 4 # Desigualdade (Excel usa 3 <> 4)
Out[18]: True
In [19]: 3 < 4 # Menor que. Use > para maior que.
Out[19]: True
In [20]: 3 <= 4 # Menor ou igual. Use >= para maior ou igual.
Out[20]: True
In [21]: # Você pode encadear expressões lógicas
# Em VBA, seria: 10 < 12 And 12 < 17
# Em fórmulas do Excel, seria: =AND(10 < 12, 12 < 17)
10 < 12 < 17
Out[21]: True
In [22]: not True # operador "not"
Out[22]: False
In [23]: False e True # operador "and"
Out[23]: False
In [24]: False or True # operador "or"
Out[24]: True
Todo objeto Python é avaliado como True ou False. A maioria dos objetos são True, mas
há alguns que avaliam como False incluindo None (veja a barra lateral), False, 0 ou tipos
de dados vazios, por exemplo, uma string vazia (apresentarei strings na próxima seção).
None
None é uma constante interna e representa “a ausência de um valor” de acordo com os
documentos oficiais. Por exemplo, se uma função não retornar nada explicitamente, ela
retornará None. Também é uma boa opção representar células vazias no Excel, como
veremos na Parte III e Parte IV.
Com booleanos em nosso bolso, resta mais um tipo de dado básico: dados textuais, mais
conhecidos como strings.
Strings
Se você já trabalhou com strings no VBA que são maiores que uma linha e contêm variáveis
e aspas literais, provavelmente gostaria que fosse mais fácil. Felizmente, esta é uma área onde
o Python é particularmente forte.strings podem ser expressas usando aspas duplas (") ou
aspas simples ('). A única condição é que você tenha que iniciar e terminar a string com o
mesmo tipo de aspas. Você pode usar + para concatenar strings ou * para repetir Como já
mostrei a você o caso de repetição ao testar o Python REPL no capítulo anterior, aqui está
um exemplo usando o sinal de mais:
In [30]: "A double quote string. " + 'A single quote string.'
Out[30]: 'A double quote string. A single quote string.'
Dependendo do que você deseja escrever, usar aspas simples ou duplas pode ajudá-lo a
imprimir facilmente aspas literais sem a necessidade de escapar delas. Se você ainda precisar
escapar de um caractere, preceda-o com uma barra invertida:
In [31]: print("Don't wait! " + 'Learn how to "speak" Python.')
Don't wait! Learn how to "speak" Python.
In [32]: print("It's easy to \"escape\" characters with a leading \\.")
It's easy to "escape" characters with a leading \.
Quando você está misturando strings com variáveis, você normalmente trabalha com f-
strings, abreviatura de formatted string literal. Basta colocar um f na frente de sua string
e use variáveis entre chaves:
In [33]: # Note how Python allows you to conveniently assign multiple
# values to multiple variables in a single line
first_adjective, second_adjective = "free", "open source"
f"Python is {first_adjective} and {second_adjective}."
Out[33]: 'Python is free and open source.'
Página 55 Capítulo 3
Como mencionei no início desta seção, strings são objetos como todo o resto e oferecem
alguns métodos (ou seja, funções) para realizar uma ação nessa string. Por exemplo, é assim
que você transforma letras maiúsculas e minúsculas:
In [34]: "PYTHON".lower()
Out[34]: 'python'
In [35]: "python".upper()
Out[35]: 'PYTHON'
Obtendo ajuda
Como você sabe quais atributos certos objetos como strings oferecem e quais argumentos
seus métodos aceitam? A resposta depende um pouco da ferramenta que você usa: com
notebooks Jupyter, pressione a tecla Tab depois de digitar o ponto que segue um objeto,
por exemplo "python".<Tab>. Isso fará com que um dropdown apareça com todos os
atributos e métodos que este objeto oferece. Se o cursor estiver em um método, por exemplo,
entre parênteses de "python".upper(), pressione Shift+Tab para obter a descrição dessa
função. O VS Code exibirá essas informações automaticamente como uma dica de
ferramenta. Se você executar um Python REPL no prompt do Anaconda, use
dir("python") para obter os atributos disponíveis e help("python".upper) para
imprimir a descrição do método upper. Fora isso, é sempre uma boa ideia voltar ao Python
online documentation. Se você está procurando a documentação de pacotes de terceiros
como pandas, é útil procurá-los em PyPI, O índice de pacotes do Python, onde você
encontrará os links para as respectivas home pages e documentação.
Ao trabalhar com strings, uma tarefa normal é selecionar partes de uma string: por exemplo,
você pode querer obter a USD parte EURUSD notação da taxa de câmbio. A próxima seção
mostra o poderoso mecanismo de indexação e fatiamento do Python que permite fazer
exatamente isso.
Indexação e fatiamento
A indexação e o fatiamento fornecem acesso a elementos específicos de uma sequência.
Como as strings são sequências de caracteres, podemos usá-las para aprender como
funciona. Na próxima seção, conheceremos sequências adicionais como listas e tuplas que
também suportam indexação e fatiamento.
Página 56 Capítulo 3
Indexação
Figura 3-1 apresenta o conceito de indexação. Python é baseado em zero, o que significa
que o primeiro elemento em uma sequência é referido pelo índice 0. Índices negativos de -
1 permitem que você faça referência a elementos do final da sequência.
Muitas vezes você desejará extrair mais do que apenas um único caractere — é aqui que
entra o fatiamento.
Página 57 Capítulo 3
Fatiar
Se você deseja obter mais de um elemento de uma sequência, use a fatiamento , que
funciona da seguinte maneira:
sequence[start:stop:step]
Python usa intervalos semi-abertos: o índice start é incluído enquanto o índice stop não.
Se você deixar os argumentos start ou stop ausentes, ele incluirá tudo desde o início ou
até o final da sequência, respectivamente. step determina a direção e o tamanho do passo:
por exemplo, 2 retornará cada segundo elemento da esquerda para a direita e -3 retornará
cada terceiro elemento da direita para a esquerda. O tamanho padrão do passo é um:
In [41]: language[:3] # Same as language[0:3]
Out[41]: 'PYT'
In [42]: language[1:3]
Out[42]: 'YT'
In [43]: language[-3:] # Same as language[-3:6]
Out[43]: 'HON'
In [44]: language[-3:-1]
Out[44]: 'HO'
In [45]: language[::2] # Every second element
Out[45]: 'PTO'
In [46]: language[-1:-4:-1] # Negative step goes from right to left
Out[46]: 'NOH'
Até agora vimos apenas uma única operação de índice ou fatia, mas o Python também
permite encadear várias operações de índice e fatia juntas. Por exemplo, se você deseja obter
o segundo caractere dos três últimos caracteres, você pode fazer assim:
In [47]: language[-3:][1]
Out[47]: 'O'
Este é o mesmo que language[-2] então, neste caso, não faria muito sentido usar
encadeamento, mas fará mais sentido quando usarmos indexação e fatiamento com listas,
uma das estruturas de dados que vou introduzir em a próxima seção.
Estruturas de dados
Python oferece estruturas de dados poderosas que facilitam muito o trabalho com uma
coleção de objetos. Nesta seção, apresentarei listas, dicionários, tuplas e conjuntos. Embora
cada uma dessas estruturas de dados tenha características ligeiramente diferentes, todas elas
são capazes de conter vários objetos.
Página 58 Capítulo 3
No VBA, você pode ter usado coleções ou matrizes para armazenar vários valores. O VBA
ainda oferece uma estrutura de dados chamada dicionário que funciona conceitualmente
da mesma forma que o dicionário do Python. No entanto, está disponível apenas na versão
Windows do Excel pronto para uso. Vamos começar com as listas, a estrutura de dados que
você provavelmente mais usará.
Listas
listas são capazes de conter vários objetos de diferentes tipos de dados. Eles são tão versáteis
que você os usará o tempo todo. Você cria uma lista da seguinte forma:
[element1, element2, ...]
Aqui estão duas listas, uma com os nomes dos arquivos do Excel e outra com alguns
números:
In [48]: file_names = ["one.xlsx", "two.xlsx", "three.xlsx"]
numbers = [1, 2, 3]
Assim como as strings, as listas podem ser facilmente concatenadas com o sinal de mais. Isso
também mostra que as listas podem conter diferentes tipos de objetos:
In [49]: file_names + numbers
Out[49]: ['one.xlsx', 'two.xlsx', 'three.xlsx', 1, 2, 3]
Como as listas são objetos como todo o resto, as listas também podem ter outras listas como
seus elementos. Vou me referir a eles como listas aninhadas:
In [50]: nested_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Se você reorganizar isso para abranger várias linhas , você pode reconhecer facilmente que
esta é uma representação muito boa de uma matriz ou um intervalo de células da planilha.
Observe que os colchetes implicitamente permitem que você quebre as linhas (consulte a
barra lateral).meio de indexação e fatiamento, você obtém os elementos desejados:
In [51]: cells = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
In [52]: cells[1] # Second row
Out[52]: [4, 5, 6]
In [53]: cells[1][1:] # Second row, second and third column
Out[53]: [5, 6]
Página 59 Capítulo 3
Continuação da linha
Às vezes, uma linha de código pode ficar tão longa que você precisará dividi-la em duas ou
mais linhas para manter seu código legível. Tecnicamente, você pode usar parênteses ou uma
barra invertida para quebrar a linha:
In [54]: a = (1 + 2
+ 3)
In [55]: a = 1 + 2 \
+ 3
O guia de estilo do Python, no entanto, prefere que você use quebras de linha implícitas,
se possível: sempre que estiver usando uma expressão que contenha parênteses, colchetes ou
chaves, use-as para introduzir uma quebra de linha sem precisar introduzir um caractere
adicional. Falarei mais sobre o guia de estilo do Python no final deste capítulo.
Para excluir um elemento, use pop ou del. Enquanto pop é um método, del é
implementado como uma declaração em Python:
In [59]: users.pop() # Removes and returns the last element by default
Out[59]: 'Jennifer'
In [60]: users
Out[60]: ['Kim', 'Linda', 'Brian']
In [61]: del users[0] # del removes an element at the given index
Algumas outras coisas úteis que você pode fazer com listas são:
In [62]: len(users) # Length
Out[62]: 2
In [63]: "Linda" in users # Check if users contains "Linda"
Out[63]: True
Página 60 Capítulo 3
In [64]: print(sorted(users)) # Returns a new sorted list
print(users) # The original list is unchanged
['Brian', 'Linda']
['Linda', 'Brian']
In [65]: users.sort() # Sorts the original list
users
Out[65]: ['Brian', 'Linda']
Para obter acesso a elementos em uma lista, você se refere a eles por sua posição ou índice —
isso nem sempre é prático. Dicionários, o tópico da próxima seção, permitem que você tenha
acesso a elementos por meio de uma chave (geralmente um nome).
Dicionários
Dicionários mapeiam chaves para valores. Você encontrará combinações de chave/valor o
tempo todo. A maneira mais fácil de criar um dicionário é a seguinte:
{key1: value1, key2: value2, ...}
Enquanto as listas permitem acessar elementos por índice, ou seja, posição, os dicionários
permitem acessar elementos por chave. Tal como acontece com os índices, as chaves são
acessadas por meio de colchetes. Os exemplos de código a seguir usarão um par de moedas
(chave) que mapeia para a taxa de câmbio (valor):
In [68]: exchange_rates = {"EURUSD": 1.1152,
"GBPUSD": 1.2454,
"AUDUSD": 0.6161}
In [69]: exchange_rates["EURUSD"] # Access the EURUSD exchange rate
Out[69]: 1.1152
Os exemplos a seguir mostram como alterar valores existentes e adicionar novos pares
chave/valor:
In [70]: exchange_rates["EURUSD"] = 1.2 # Alterar um valor existente
exchange_rates
Out[70]: {'EURUSD': 1.2, 'GBPUSD': 1.2454, 'AUDUSD': 0.6161}
In [71]: exchange_rates["CADUSD"] = 0.714 # Adicionar uma nova chave /value pair
exchange_rates
Out[71]: {'EURUSD': 1.2, 'GBPUSD': 1.2454, 'AUDUSD': 0.6161, 'CADUSD': 0.714}
Página 61 Capítulo 3
A maneira mais fácil de mesclar dois ou mais dicionários é descompactá -los em um novo 1.
Você descompacta um dicionário usando dois asteriscos à esquerda. Se o segundo dicionário
contiver chaves do primeiro, os valores do primeiro serão substituídos. Você pode ver isso
acontecendo observando a taxa de câmbio GBPUSD:
In [72]: {**exchange_rates, **{"SGDUSD": 0.7004, "GBPUSD": 1.2222}}
Out[72]: {'EURUSD': 1.2,
'GBPUSD': 1.2222,
'AUDUSD': 0.6161,
'CADUSD': 0.714,
'SGDUSD': 0.7004}
Python 3.9 introduziu o caractere pipe como um operador de mesclagem dedicado para
dicionários, o que permite simplificar a expressão anterior para isso:
exchange_rates | {"SGDUSD": 0.7004, "GBPUSD": 1.2222}
Muitos objetos podem servir como chaves; o seguinte é um exemplo com números inteiros:
In [73]: currencies = {1: "EUR", 2: "USD", 3: "AUD"}
In [74]: currencies[1]
Out[74]: 'EUR'
Usando o método get, os dicionários permitem que você use um valor padrão caso a chave
não exista:
In [75]: # currencies[100] would raise an exception. Instead of 100,
# you could use any other non-existing key, too.
currencies.get(100, "N/A")
Out[75]: 'N/A'
Dicionários geralmente podem ser usados quando você usaria uma instrução Case no VBA.
O exemplo anterior poderia ser escrito assim no VBA:
Select Case x
Case 1
Debug.Print "EUR"
Case 2
Debug.Print "USD"
Case 3
Debug.Print "AUD"
Case Else
Debug.Print "N/A"
End Select
Agora que você já sabe como trabalhar com dicionários, vamos para a próxima estrutura de
dados: tuplas. Eles são semelhantes às listas com uma grande diferença, como veremos na
próxima seção.
Página 62 Capítulo 3
Tuplas
Tuplas são semelhantes a listas com a diferença de que são imutáveis: uma vez criadas, seus
elementos não podem ser alterados. Embora muitas vezes você possa usar tuplas e listas de
forma intercambiável, as tuplas são a escolha óbvia para uma coleção que nunca muda ao
longo do programa. As tuplas são criadas separando os valores com vírgulas:
mytuple = element1, element2, ...
Conjuntos
Conjuntos são coleções que não possuem elementos duplicados. Embora você possa usá-
los para operações de teoria de conjuntos, na prática eles geralmente ajudam a obter os
valores exclusivos de uma lista ou tupla. Você cria conjuntos usando chaves:
{element1, element2, ...}
Para obter os objetos exclusivos em uma lista ou tupla, use o construtor set assim:
In [79]: set(["USD", "USD", "SGD", "EUR", "USD", "EUR"])
Out[79]: {'EUR', 'SGD', 'USD'}
Fora isso, você pode aplicar operações de teoria de conjuntos como interseção e união:
In [80]: portfolio1 = {"USD", "EUR", "SGD", "CHF"}
portfolio2 = {"EUR", "SGD", "CAD"}
In [81]: # Same as portfolio2.union(portfolio1)
portfolio1.union(portfolio2)
Out[81]: {'CAD', 'CHF', 'EUR', 'SGD', 'USD'}
In [82]: # Same as portfolio2.intersection(portfolio1)
portfolio1.intersection(portfolio2)
Página 63 Capítulo 3
Out[82]: {'EUR', 'SGD'}
Para uma visão geral completa das operações de conjuntos, consulte os documentos oficiais.
Antes de prosseguir, vamos revisar rapidamente as quatro estruturas de dados que acabamos
de conhecer na Tabela 3-1. Ele mostra uma amostra para cada estrutura de dados na notação
que usei nos parágrafos anteriores, os chamados literais. Além disso, também estou listando
seus construtores que oferecem uma alternativa ao uso de literais e são frequentemente
usados para converter de uma estrutura de dados para outra. Por exemplo, para converter
uma tupla em uma lista, faça:
In [83]: currencies = "USD", "EUR", "CHF"
currencies
Out[83]: ('USD', 'EUR', 'CHF')
In [84]: list(currencies)
Out[84]: ['USD', 'EUR', 'CHF']
Neste ponto, você conhece todos os tipos de dados importantes, incluindo os básicos, como
floats e strings e estruturas de dados como listas e dicionários. Na próxima seção, passamos
para controlar o fluxo.
Control Flow
Esta seção apresenta a instrução if, bem como os loops for e while. A instrução if
permite que você execute certas linhas de código somente se uma condição for atendida, e
os loops for e while executarão um bloco de código repetidamente. No final da seção,
também apresentarei as compreensões de listas, que são uma maneira de construir listas que
podem servir como uma alternativa aos loops for. Iniciarei esta seção com a definição de
blocos de código, para os quais também preciso apresentar uma das particularidades mais
notáveis do Python: espaço em branco significativo.
A linha que precede o bloco de código sempre termina com dois pontos. Como o final do
bloco de código é atingido quando você não recua mais a linha, você precisa usar a instrução
pass se quiser criar um bloco de código fictício que não faz nada. No VBA, isso
corresponderia ao seguinte:
If condition Then
' Do nothing
End If
Agora que você sabe como definir blocos de código, vamos começar a usá-los na próxima
seção, onde apresentarei corretamente a instrução if.
Se você faria o mesmo que fizemos no Capítulo 1, ou seja, recue as instruções elif e else,
você obteria um SyntaxError. O Python não permitirá que você recue seu código de
maneira diferente da lógica. Comparado ao VBA, as palavras-chave são minúsculas e, em vez
de ElseIf no VBA, o Python usa elif. Declarações if são uma maneira fácil de saber se
um programador é novo em Python ou se já adotou um Pythonic: em Python, uma instrução
simples if não requer nenhum parêntese em volta e para testar se um valor é True, você não
precisa fazer isso explicitamente. Aqui está o que quero dizer com isso:
In [86]: is_important = True
if is_important:
print("This is important.")
else:
print("This is not important.")
This is important.
Página 65 Capítulo 3
O mesmo funciona se você quiser verificar se uma sequência como uma lista está vazia ou
não:
In [87]: values = []
if values:
print(f"The following values were provided: {values}")
else:
print("There were no values provided.")
There were no values provided.
Com declarações if e expressões condicionais em nosso bolso, vamos voltar nossa atenção
para loops for e while na próxima seção.
USD
HKD
AUD
Como uma nota lateral, a instrução For Each está próxima de como o loop for do Python
funciona. O exemplo anterior poderia ser escrito assim em VBA:
Dim currencies As Variant
Dim curr As Variant 'currency is a reserved word in VBA
range avalia lentamente, o que significa que sem pedir explicitamente, você não verá a
sequência que ele gera:
In [90]: range(5)
Out[90]: range(0, 5)
Na maioria das vezes, não há necessidade de envolver range com uma list:
In [93]: for i in range(3):
print(i)
0
1
2
Se você precisar de uma variável de contador durante o loop uma sequência, use enumerate.
Ele retorna uma sequência de tuplas (index, element). Por padrão, o índice começa em
zero e é incrementado em um. Você pode usar enumerate em um loop como este:
In [94]: for i, currency in enumerate(currencies):
print(i, currency)
0 USD
1 HKD
2 AUD
Fazer um loop sobre tuplas e conjuntos funciona da mesma forma que com listas. Quando
você faz um loop nos dicionários, o Python faz um loop nas chaves:
Página 67 Capítulo 3
In [95]: exchange_rates = {"EURUSD": 1.1152,
"GBPUSD": 1.2454,
"AUDUSD": 0.6161}
for currency_pair in exchange_rates:
print(currency_pair)
EURUSD
GBPUSD
AUDUSD
Usando o método items, você obtém a chave e o valor ao mesmo tempo que a tupla:
In [96]: for currency_pair, exchange_rate in exchange_rates.items():
print(currency_pair, exchange_rate)
EURUSD 1.1152
GBPUSD 1.2454
AUDUSD 0.6161
Você pula o restante de um loop com a instrução continue, o que significa que a execução
continua com um novo loop e o próximo elemento:
In [98]: for i in range(4):
if i == 2:
continue
else:
print(i)
0
1
3
Ao comparar loops for em VBA com Python, há uma diferença sutil: em VBA, a variável
counter aumenta além do seu limite superior após terminar o loop:
For i = 1 To 3
Debug.Print i
Next i
Debug.Print i
Página 68 Capítulo 3
Isso imprime:
1
2
3
4
Em vez de de fazer um loop em uma sequência, você também pode usar while loops para
executar um loop enquanto uma determinada condição for verdadeira:
In [100]: n = 0
while n <= 2:
print(n)
n += 1
0
1
2
Augmented Assignment
Eu usei a atribuição aumentada notação de n += 1. Isso é o mesmo que escrever
n = n + 1. Ele também funciona com todos os outros operadores matemáticos
que apresentei anteriormente; por exemplo, para menos, você pode escrever n -=
1.
Muitas vezes, você precisará coletar certos elementos em uma lista para processamento
posterior. Nesse caso, o Python oferece uma alternativa para escrever loops: lista, dicionário
e compreensão de conjuntos.
Isso geralmente é mais fácil de escrever com uma compreensão da lista. Uma compreensão
de lista é uma maneira concisa de criar uma lista. Você pode pegar sua sintaxe neste exemplo,
que faz o mesmo que o loop anterior for:
In [103]: [pair[:3] for pair in currency_pairs if pair[3:] == "USD"]
Out[103]: ['AUD', 'NZD']
Se você não tiver nenhuma condição para satisfazer, simplesmente deixe a if de lado. Por
exemplo, para inverter todos os pares de moedas para que a primeira moeda venha em
segundo lugar e vice-versa, você faria:
In [104]: [pair[3:] + pair[:3] for pair in currency_pairs]
Out[104]: ['JPYUSD', 'GBPUSD', 'CHFUSD', 'CADUSD', 'USDAUD', 'USDNZD']
Neste ponto, você já pode escreva scripts simples, pois você conhece a maioria dos blocos de
construção básicos do Python. Na próxima seção, você aprenderá como organizar seu código
para mantê-lo sustentável quando seus scripts começarem a ficar maiores.
Organização do código
Nesta seção, veremos como trazer o código para uma estrutura sustentável: Vou começar
apresentando funções com todos os detalhes que você normalmente precisará antes de
mostrar como dividir seu código em diferentes Python módulos. O conhecimento sobre
módulos nos permitirá terminar esta seção examinando o módulo datetime que faz parte
da biblioteca padrão.
Página 70 Capítulo 3
Funções
Mesmo que você use Python apenas para scripts simples, você ainda escreverá funções
regularmente: elas são uma das construções mais importantes de toda linguagem de
programação e permitem que você reutilize as mesmas linhas de código de qualquer lugar
em seu programa. Começaremos esta seção definindo uma função antes de vermos como
chamá-la!
Definindo funções
Para escrever sua própria função em Python, você deve usar a palavra-chave def, que
significa definição. Ao contrário do VBA, o Python não diferencia entre uma função e um
procedimento Sub. Em Python, o equivalente a um procedimento Sub é simplesmente uma
função que não retorna nada. Funções em Python seguem a sintaxe para blocos de código,
ou seja, você termina a primeira linha com dois pontos e recua o corpo da função:
def function_name(required_argument, optional_argument=default_value, ...):
return value1, value2, ...
argumentos
obrigatórios não têm um valor padrão. Vários argumentos são separados por vírgulas.
Argumentos opcionais
Você torna um argumento opcional fornecendo um valor padrão. None é
frequentemente usado para tornar um argumento opcional se não houver um padrão
significativo.
Valor de retorno
A instrução return define o valor que a função retorna. Se você deixá-lo de lado, a
função retornará automaticamente None. O Python convenientemente permite
retornar vários valores separados por vírgulas.
Para poder brincar com uma função, vamos definir uma que seja capaz de converter a
temperatura de Fahrenheit ou Kelvin para graus Celsius:
In [107]: def convert_to_celsius(degrees, source="fahrenheit"):
if source.lower() == "fahrenheit":
return (degrees-32) * (5/9)
elif source.lower() == "kelvin":
return degrees - 273.15
else:
return f"Don't know how to convert from {source}"
Estou usando o método string lower, que transforma as strings fornecidas em minúsculas.
Isso nos permite aceitar a string source com qualquer capitalização enquanto a
comparação ainda funcionará. Com a função convert_to_celsius definida, vamos ver
como podemos chamá-la!
Página 71 Capítulo 3
Chamando funções
Como mencionado brevemente no início deste capítulo, você chama uma função
adicionando parênteses ao nome da função, colocando os argumentos da função:
value1, value2, ... = function_name(positional_arg, arg_name=value, ...)
Posicional argumentos
Se você fornecer um valor como argumento posicional (positional_arg), os valores
serão correspondidos aos argumentos de acordo com sua posição na definição da
função.
Keyword arguments
Ao fornecer o argumento no formato arg_name=value, você está fornecendo um
argumento de palavra-chave. Isso tem a vantagem de que você pode fornecer os
argumentos em qualquer ordem. Também é mais explícito para o leitor e pode torná-
lo mais fácil de entender. Por exemplo, se a função for definida como f(a, b), você
pode chamar a função assim: f(b=1, a=2). Esse conceito também existe no VBA, onde
você pode usar argumentos de palavras-chave chamando uma função como esta:
f(b:=1, a:=1).
Vamos brincar com a função convert_to_celsius para ver como tudo isso funciona na
prática:
In [108]: convert_to_celsius(100, "fahrenheit") # Positional arguments
Out[108]: 37.77777777777778
In [109]: convert_to_celsius(50) # Will use the default source (fahrenheit)
Out[109]: 10.0
In [110]: convert_to_celsius(source="kelvin", degrees=0) # Keyword arguments
Out[110]: -273.15
Agora que você sabe como definir e chamar funções, vamos ver como organizá-los com a
ajuda de módulos.
Para poder importar o módulo temperature do seu notebook Jupyter, você precisará do
notebook Jupyter e do módulo temperature estar no mesmo diretório - como no caso do
repositório complementar. Para importar, você só usa o nome do módulo, sem a terminação
.py. Após executar a instrução import, você terá acesso a todos os objetos desse módulo
Python por meio da notação de ponto. Por exemplo, use
temperature.convert_to_celsius() para realizar sua conversão:
In [111]: import temperature
This is the temperature module.
In [112]: temperature.TEMPERATURE_SCALES
Out[112]: ('fahrenheit', 'kelvin', 'celsius')
In [113]: temperature.convert_to_celsius(120, "fahrenheit")
Out[113]: 48.88888888888889
Observe que usei letras maiúsculas para TEMPERATURE_SCALES para expressar que é uma
constante – falarei mais sobre isso no final deste capítulo. Quando você executa a célula com
import temperature, Python irá executar o arquivo temperature.py de cima para baixo.
Você pode ver facilmente isso acontecendo, pois a importação do módulo acionará a função
de impressão na parte inferior do temperature.py.
Atribuir um alias curto tp ao seu módulo pode torná-lo mais fácil de usar enquanto ainda
é sempre claro de onde vem um objeto. Muitos pacotes de terceiros sugerem uma convenção
específica ao usar um alias. Por exemplo, pandas está usando import pandas as pd. Existe
mais uma opção para importar objetos de outro módulo:
In [116]: from temperature import TEMPERATURE_SCALES, convert_to_celsius
In [117]: TEMPERATURE_SCALES
Out[117]: ('fahrenheit', 'kelvin', 'celsius')
A pasta pycache
Ao importar o módulo temperature, você verá que o Python cria uma pasta
chamada pycache com arquivos que possuem a extensão .pyc. Esses são
arquivos compilados por bytecode que o interpretador Python cria quando você
importa um módulo. Para nossos propósitos, podemos simplesmente ignorar esta
pasta, pois é um detalhe técnico de como o Python executa seu código.
Ao usar a sintaxe from x import y, você importa apenas objetos específicos. Ao fazer isso,
você está importando-os diretamente para o namespace do seu script principal: ou seja, sem
examinar as instruções import, você não poderá dizer se os objetos importados foram
definidos em seu script Python atual ou no notebook Jupyter ou se eles vêm de outro
módulo. Isso pode causar conflitos: se seu script principal tiver uma função chamada
convert_to_celsius, ela substituirá aquela que você está importando do módulo
temperature. Se, no entanto, você usar um dos dois métodos anteriores, sua função local e
a do módulo importado podem ficar próximas uma da outra como convert_to_celsius
e temperature.convert_to_celsius.
A classe datetime
Trabalhar com data e hora é uma operação comum no Excel, mas vem com limitações: por
exemplo, o formato de célula do Excel para hora não suporta unidades menores que
milissegundos e fusos horários não são suportados. No Excel, a data e a hora são
armazenadas como um float simples chamado número de série da data. A célula do Excel
é formatada para exibi-la como data e/ou hora. Por exemplo, 1º de janeiro de 1900 tem a
data número de série 1, o que significa que essa também é a data mais antiga com a qual você
pode trabalhar no Excel. O tempo é convertido na parte decimal do float, por exemplo,
01/01/190010:10:00 é representado por 1.4236111111.
Em Python, para trabalhar com data e hora, você importa o módulo datetime, que faz
parte da biblioteca padrão. O módulo datetime contém uma classe com o mesmo nome
que nos permite criar objetos datetime. Como ter o mesmo nome para o módulo e a classe
pode ser confuso, usarei a seguinte convenção de importação ao longo deste livro: import
datetime as dt. Isso facilita a diferenciação entre o módulo ( dt) e a classe (datetime).
Até este ponto, estávamos na maioria das vezes usando literais para criar objetos como listas
ou dicionários. Literais referem-se à sintaxe que o Python reconhece como um tipo de
objeto específico — no caso de uma lista, seria algo como [1, 2, 3]. No entanto, a maioria
dos objetos precisa ser criada chamando sua classe: esse processo é chamado instanciação e,
portanto, os objetos também são chamados de instâncias de classe. Chamar uma classe
funciona da mesma forma que chamar uma função, ou seja, você adiciona parênteses ao
nome da classe e fornece os argumentos da mesma forma que fizemos com as funções. Para
instanciar um objeto datetime, você precisa chamar a classe assim:
import datetime as dt
dt.datetime(year, month, day, hour, minute, second, microsecond, timezone)
Vamos ver alguns exemplos para ver como você trabalha com objetos datetime em
Python. Para o propósito desta introdução, vamos ignorar os fusos horários e trabalhar com
objetos ingênuos de fuso horário datetime:
In [118]: # Import the datetime module as "dt"
import datetime as dt
In [119]: # Instantiate a datetime object called "timestamp"
timestamp = dt.datetime(2020, 1, 31, 14, 30)
timestamp
Out[119]: datetime.datetime(2020, 1, 31, 14, 30)
Página 75 Capítulo 3
In [120]: # Datetime objects offer various attributes, e.g., to get the day
timestamp.day
Out[120]: 31
In [121]: # The difference of two datetime objects returns a timedelta object
timestamp - dt.datetime(2020, 1, 14, 12, 0)
Out[121]: datetime.timedelta(days=17, seconds=9000)
In [122]: # Accordingly, you can also work with timedelta objects
timestamp + dt.timedelta(days=1, hours=4, minutes=11)
Out[122]: datetime.datetime(2020, 2, 1, 18, 41)
Para formatar objetos datetime em strings, use o método strftime e para analisar uma
string e convertê-lo em um objeto datetime, use a função strptime (você pode encontrar
uma visão geral dos códigos de formato aceitos nos documentos datetime):
In [123]: # Format a datetime object in a specific way
# You could also use an f-string: f"{timestamp:%d/%m/%Y %H:%M}"
timestamp.strftime("%d/%m/%Y %H:%M")
Out[123]: '31/01/2020 14:30'
In [124]: # Parse a string into a datetime object
dt.datetime.strptime("12.1.2020", "%d.%m.%Y")
Out[124]: datetime.datetime(2020, 1, 12, 0, 0)
Após esta breve introdução ao módulo datetime, vamos seguir em frente para o último
tópico deste capítulo, que é sobre como formatar seu código corretamente.
class TemperatureConverter:
pass # Doesn't do anything at the moment
Explique o que o script/módulo faz com uma docstring no topo. Uma docstring é um
tipo especial de string, entre aspas triplas. Além de servir como uma string para
documentar seu código, uma docstring também facilita escrever strings em várias linhas
e é útil se seu texto contiver muitas aspas duplas ou aspas simples, pois você não
precisará escapá-las . Eles também são úteis para escrever consultas SQL de várias linhas,
como veremos no Capítulo 11.
Todas as importações estão no topo do arquivo, uma por linha. Liste primeiro as
importações da biblioteca padrão, depois as de pacotes de terceiros e, finalmente, as de
seus próprios módulos. Esta amostra faz uso apenas da biblioteca padrão.
Use letras maiúsculas com sublinhados para constantes. Use um comprimento máximo
de linha de 79 caracteres. Se possível, aproveite os parênteses, colchetes ou chaves para
quebras de linha implícitas.
Os comentários embutidos devem ser separados por pelo menos dois espaços do
código. Os blocos de código devem ser recuados por quatro espaços.
Use nomes em minúsculas para variáveis. Faça uso de sublinhados se eles melhorarem
a legibilidade. Ao atribuir um nome de variável, use espaços ao redor do sinal de igual.
No entanto, ao chamar uma função, não use espaços ao redor do sinal de igual usado
com argumentos de palavras-chave.
Dicas de tipo
No VBA, você geralmente vê código que prefixa cada variável com uma abreviação para o
tipo de dados, como strEmployeeName ou wbWorkbookName. Embora ninguém o impeça
de fazer isso em Python, isso geralmente não é feito. Você também não encontrará um
equivalente à instrução Option Explicit ou Dim para declarar o tipo de uma variável.
Em vez disso, o Python 3.5 introduziu um recurso chamado dicas de tipo. As dicas de tipo
também são chamadas de anotações de tipo e permitem declarar o tipo de dados de uma
variável. Eles são completamente opcionais e não têm efeito sobre como o código é
executado pelo interpretador Python (existem, no entanto, pacotes de terceiros como
pydantic que pode impor dicas de tipo em tempo de execução). O principal objetivo das
dicas de tipo é permitir que editores de texto como o VS Code detectem mais erros antes de
executar o código, mas também podem melhorar o preenchimento automático de código
do VS Code e de outros editores. O verificador de tipo mais popular para código anotado
por tipo é o mypy, que o VS Code oferece como um linter. Para ter uma ideia de como as
anotações de tipo funcionam em Python, aqui está uma pequena amostra sem dicas de tipo:
x = 1
def hello(name):
return f"Hello {name}!"
Página 79 Capítulo 3
E novamente com dicas de tipo:
x: int = 1
Como as dicas de tipo geralmente fazem mais sentido em bases de código maiores, não as
usarei no restante deste livro.
Conclusão
Este capítulo foi uma introdução completa ao Python. Conhecemos os blocos de
construção mais importantes da linguagem, incluindo estruturas de dados, funções e
módulos. Também abordamos algumas particularidades do Python, como espaço em
branco significativo e diretrizes de formatação de código, mais conhecidas como PEP 8. Para
continuar com este livro, você não precisará conhecer todos os detalhes: como iniciante,
basta saber sobre listas e dicionários , indexação e fatiamento, bem como como trabalhar
com funções, módulos, loops for e instruções if já o levará longe.
Comparado ao VBA, acho o Python mais consistente e poderoso, mas ao mesmo tempo
mais fácil de aprender. Se você é um fã obstinado do VBA e este capítulo ainda não o
convenceu, tenho certeza que a próxima parte o fará: lá, eu lhe darei uma introdução aos
cálculos baseados em array antes de iniciar nossa jornada de análise de dados com o
biblioteca de pandas. Vamos começar com a Parte II aprendendo algumas noções básicas
sobre o NumPy!
Página 80
CAPÍTULO 4
Fundamentos do NumPy
Como você deve se lembrar do Capítulo 1, o NumPy é o pacote principal para computação
científica em Python, fornecendo suporte para cálculos baseados em array e álgebra linear.
Como o NumPy é a espinha dorsal dos pandas, vou apresentar seus conceitos básicos neste
capítulo: depois de explicar o que é um array NumPy, veremos vetorização e transmissão,
dois conceitos importantes que permitem escrever código matemático conciso e que você
encontrará novamente em pandas. Depois disso, veremos por que o NumPy oferece
funções especiais chamadas funções universais antes de encerrarmos este capítulo
aprendendo como obter e definir valores de um array e explicando a diferença entre uma
visualização e uma cópia de um array NumPy. Mesmo que dificilmente usaremos o NumPy
diretamente neste livro, conhecer seus fundamentos tornará mais fácil aprender pandas no
próximo capítulo.
Introdução ao NumPy
Nesta seção, aprenderemos sobre matrizes unidimensionais e bidimensionais NumPy e o
que está por trás dos termos técnicos vetorização, transmissão e função universal.
Array NumPy
Para realizar cálculos baseados em array com listas aninhadas, como vimos no capítulo
anterior, você teria que escrever algum tipo de loop. Por exemplo, para adicionar um
número a cada elemento em uma lista aninhada, você pode usar a seguinte compreensão de
lista aninhada:
In [1]: matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
In [2]: [[i + 1 for i in row] for row in matrix]
Out[2]: [[2, 3, 4], [5, 6, 7], [8, 9, 10]]
Página 81 Capítulo 4
Isso não é muito legível e, mais importante, se você fizer isso com matrizes grandes, o loop
por cada elemento se tornará muito lento. Dependendo do seu caso de uso e do tamanho
das matrizes, cálculos com matrizes NumPy em vez de listas Python pode tornar seus
cálculos de algumas vezes a cerca de cem vezes mais rápido. O NumPy alcançar esse
desempenho fazendo uso de código que foi escrito em C ou Fortran – são linguagens de
programação compiladas que são muito mais rápidas que o Python. Um array NumPy é um
array N-dimensional para dados homogêneos. Homogêneo significa que todos os
elementos na matriz precisam ser do mesmo tipo de dados. Mais comumente, você está
lidando com arrays unidimensionais e bidimensionais de floats conforme mostrado
esquematicamente na Figura 4-1.
Dimensão do Array
É importante notar a diferença entre um array unidimensional e bidimensional: um
array unidimensional tem apenas um eixo e, portanto, não tem uma orientação
explícita de coluna ou linha. Embora isso se comporte como arrays no VBA, você
pode ter que se acostumar com isso se você vier de uma linguagem como
MATLAB, onde arrays unidimensionais sempre têm uma orientação de coluna ou
linha.
Mesmo que array1 consista em inteiros, exceto pelo último elemento (que é um float), a
homogeneidade dos arrays NumPy força o tipo de dados do array a ser float64, que é capaz
de acomodar todos os elementos. Para aprender sobre o tipo de dado de um array, acesse seu
atributo dtype:
Página 82 Capítulo 4
In [6]: array1.dtype
Out[6]: dtype('float64')
Como dtype retorna float64 em vez do float que vimos no último capítulo, você deve
ter adivinhado que o NumPy usa seus próprios tipos de dados numéricos, que são mais
granulares que os do Python tipos de dados. Isso geralmente não é um problema, pois na
maioria das vezes, a conversão entre os diferentes tipos de dados em Python e NumPy
acontece automaticamente. Se você precisar converter explicitamente um tipo de dados
NumPy em um dos tipos de dados básicos do Python, basta usar o construtor correspondente
(falarei mais sobre como acessar um elemento de um array em breve):
In [7]: float(array1[0] )
Out[7]: 10.0
Para uma lista completa dos tipos de dados NumPy's, veja os documentos NumPy . Com
arrays NumPy, você pode escrever um código simples para realizar cálculos baseados em
array, como veremos a seguir.
Vetorização e transmissão
Se você construir a soma de um escalar e um array NumPy, o NumPy executará uma
operação elementar, o que significa que você não precisa percorrer os elementos sozinho. A
comunidade NumPy se refere a isso como vetorização. Ele permite que você escreva um
código conciso, representando praticamente a notação matemática:
In [8]: array2 + 1
Out[8]: array([[2., 3., 4.],
[5., 6., 7. ]])
Escalar
Escalar refere-se a um tipo de dado básico Python como um float ou uma string.
Isso é para diferenciá-los de estruturas de dados com vários elementos, como listas
e dicionários ou matrizes de uma e duas dimensões NumPy.
O mesmo princípio se aplica quando você trabalha com dois arrays: NumPy executa a
operação por elemento:
In [9]: array2 * array2
Out[9]: array([[ 1., 4., 9.],
[16., 25., 36.]])
Se você usar dois arrays com formas diferentes em uma operação aritmética, o NumPy
estende — se possível — o array menor automaticamente pelo array maior para que suas
formas se tornem compatíveis. Isso é chamado de broadcast:
Página 83 Capítulo 4
In [10]: array2 * array1
Out[10]: array([[ 10., 200., 3000.],
[ 40., 500., 6000.]])
Não se intimide com a terminologia que eu introduzidos nesta seção, como escalar,
vetorização ou transmissão! Se você já trabalhou com matrizes no Excel, tudo isso deve
parecer muito natural, conforme mostrado na Figura 4-2. A captura de tela foi tirada de
array_calculations.xlsx, que você encontrará no diretório xl do repositório
complementar.
Você sabe agora que matrizes realizam operações aritméticas elemento a elemento, mas
como você pode aplicar uma função em cada elemento em uma matriz? É para isso que as
funções universais estão aqui.
1 Se já faz algum tempo desde sua última aula de álgebra linear, você pode pular este exemplo — a multiplicação de
matrizes não é algo sobre o qual este livro se baseia.
Página 84 Capítulo 4
-------------------------------------------------- -------------------------
TypeError Traceback (most recent call last)
<ipython-input-13-5c37e8f41094> in <module>
----> 1 math.sqrt(array2) # This will raise en Error
Isso irá funciona nos casos em que o NumPy não oferece um ufunc e a matriz é pequena o
suficiente. No entanto, se o NumPy tiver um ufunc, use-o, pois será muito mais rápido com
matrizes grandes — além de ser mais fácil de digitar e ler:
In [15]: np.sqrt(array2)
Out[15]: array([[1. , 1.41421356, 1.73205081],
[2. , 2.23606798, 2.44948974]])
Alguns dos ufuncs do NumPy, como sum, também estão disponíveis como métodos de
array: se você quiser o soma de cada coluna, faça o seguinte:
In [16]: array2.sum(axis=0) # Returns a 1d array
Out[16]: array([5., 7., 9.])
O argumento axis=0 refere-se ao eixo ao longo das linhas enquanto axis=1 refere-se ao
eixo ao longo as colunas, conforme ilustrado na Figura 4-1. Deixando o argumento axis
de lado resume todo o array:
In [17]: array2.sum()
Out[17]: 21.0
Você conhecerá mais ufuncs NumPy ao longo deste livro, pois eles podem ser usados com
pandas DataFrames.
Até agora, sempre trabalhamos com o array inteiro. A próxima seção mostra como
manipular partes de um array e apresenta alguns construtores de array úteis.
Outra necessidade comum, para exemplo para simulações de Monte Carlo, é gerar arrays de
números pseudo-aleatórios normalmente distribuídos. O NumPy facilita isso:
In [24]: np.random.randn(2, 3) # 2 rows, 3 columns
Out[24]: array([[-0.30047275, -1.19614685, -0.13652283],
[ 1.05769357, 0.03347978, -1.2153504 ]])
Outros construtores úteis que valem a pena explorar são np.ones e np.zeros para criar
arrays com uns e zeros, respectivamente, e np.eye para criar uma matriz identidade.
Encontraremos alguns desses construtores novamente no próximo capítulo, mas, por
enquanto, vamos aprender sobre a diferença entre uma visualização e uma cópia de um array
NumPy.
Se não for isso que você deseja, você teria que alterar In [26] da seguinte forma:
subset = array2[:, :2].copy()
Conclusão
Neste capítulo, mostrei como trabalhar com arrays NumPy e o que está por trás de
expressões como vetorização e transmissão. Colocando esses termos técnicos de lado,
trabalhar com arrays deve ser bastante intuitivo, já que eles seguem a notação matemática
muito de perto. Embora o NumPy seja uma biblioteca incrivelmente poderosa, existem dois
problemas principais quando você deseja usá-lo para análise de dados:
• Todo o array NumPy precisa ser do mesmo tipo de dados. Isso, por exemplo, significa
que você não pode realizar nenhuma das operações aritméticas que fizemos neste
capítulo quando seu array contém uma mistura de texto e números. Assim que o texto
estiver envolvido, o array terá o tipo de dados object, que não permitirá operações
matemáticas.
• O uso de arrays NumPy para análise de dados torna difícil saber a que cada coluna ou
linha se refere porque você normalmente seleciona colunas por meio de sua posição,
como em array2[:, 1].
O pandas resolveu esses problemas fornecendo estruturas de dados mais inteligentes sobre
os arrays NumPy. O que são e como funcionam é o tema do próximo capítulo.
Página 88
CAPÍTULO 5
Análise de dados com pandas
DataFrame e Series
DataFrame e Series são as principais estruturas de dados em pandas. Nesta seção, estou
apresentando-os com foco nos principais componentes de um DataFrame: índice, colunas
e dados. Um DataFrame é semelhante a um array bidimensional NumPy, mas vem com
rótulos de coluna e linha e cada coluna pode conter diferentes tipos de dados. Ao extrair
uma única coluna ou linha de um DataFrame, você obtém uma série unidimensional.
Novamente, uma série é semelhante a um array unidimensional NumPy com rótulos.
Página 89 Capítulo 5
Quando você observa a estrutura de um DataFrame na Figura 5-1, não é preciso muita
imaginação para ver que DataFrames serão suas planilhas baseadas em Python.
Para mostrar como é fácil fazer a transição de uma planilha para um DataFrame, considere
a seguinte tabela do Excel na Figura 5-2, que mostra os participantes de um curso online
com sua pontuação. Você encontrará o arquivo correspondente course_participants.xlsx
na pasta xl do repositório complementar.
Para disponibilizar esta tabela Excel em Python, comece importando pandas, então use sua
função read_excel, que retorna um DataFrame:
In [1]: import pandas as pd
In [2]: pd.read_excel("xl/course_participants.xlsx")
Out[2]: user_id name age country score continent
0 1001 Mark 55 Italy 4.5 Europe
1 1000 John 33 USA 6.7 America
2 1002 Tim 41 USA 3.9 America
3 1003 Jenny 12 Germany 9.0 Europe
Se você estiver interessado apenas no tipo de dados de suas colunas, execute df.dtypes.
Colunas com strings ou tipos de dados mistos terão o tipo de dados object. 1 Vamos agora
dar uma olhada no índice e nas colunas de um DataFrame.
1 pandas 1.0.0 introduziu uma string para tornar algumas operações mais fáceis e mais consistentes com o texto.
Como ainda é experimental, não vou fazer uso dele neste livro.
Página 91 Capítulo 5
Index
Os rótulos de linha de um DataFrame são chamados de index. Se você não tiver um índice
significativo, deixe-o de lado ao construir um DataFrame. pandas criará automaticamente
um índice inteiro começando em zero. Vimos isso no primeiro exemplo quando lemos o
DataFrame do arquivo Excel. Um índice permitirá que os pandas procurem dados mais
rapidamente e é essencial para muitas operações comuns, por exemplo, combinar dois
DataFrames. Você acessa o objeto de índice da seguinte forma:
In [5]: df.index
Out[5]: Int64Index([1001, 1000, 1002, 1003], dtype='int64')
Se fizer sentido, dê um nome ao índice. Vamos seguir a tabela no Excel e dar a ela o nome
user_id:
In [6]: df.index.name = "user_id"
df
Out[6]: name age country score continent
user_id
1001 Mark 55 Italy 4.5 Europe
1000 John 33 USA 6.7 America
1002 Tim 41 USA 3.9 America
1003 Jenny 12 Germany 9.0 Europe
Se, em vez disso, você quiser classificar as linhas por uma ou mais colunas, use
sort_values:
In [11]: df.sort_values(["continent", "age"])
Out[11]: name age country score continent
user_id
1000 John 33 USA 6.7 America
Página 93 Capítulo 5
1002 Tim 41 USA 3.9 America
1003 Jenny 12 Germany 9.0 Europe
1001 Mark 55 Italy 4.5 Europe
A amostra mostra como classificar primeiro por continent, depois por age. Se você quiser
classificar por apenas uma coluna, você também pode fornecer o nome da coluna como uma
string:
df.sort_values("continent")
Isso cobriu o básico de como os índices funcionam. Vamos agora voltar nossa atenção para
seu equivalente horizontal, as colunas DataFrame!
Colunas
Para obter informações sobre as colunas de um DataFrame, execute o seguinte código:
In [12]: df.columns
Out[12]: Index(['name', 'age', 'country', 'score', 'continent'], dtype='object')
Se você não fornecer nenhum nome de coluna ao construir um DataFrame, pandas irá
numerar as colunas com números inteiros começando em zero. Com colunas, no entanto,
isso quase nunca é uma boa ideia, pois as colunas representam variáveis e, portanto, são
fáceis de nomear. Você atribui um nome aos cabeçalhos das colunas da mesma forma que
fizemos com o índice:
In [13]: df.columns.name = "properties"
df
Out[13]: properties name age country score continent
user_id
1001 Mark 55 Italy 4.5 Europe
1000 John 33 USA 6.7 America
1002 Tim 41 USA 3.9 America
1003 Jenny 12 Germany 9.0 Europe
Se você deseja excluir colunas, use a seguinte sintaxe (o exemplo mostra como remover
colunas e índices ao mesmo tempo):
In [15]: df.drop(columns=["name", "country"],
index=[1000, 1003])
Página 94 Capítulo 5
Out[15]: properties age score continent
user_id
1001 55 4.5 Europe
1002 41 3.9 America
As colunas e o índice de um DataFrame são ambos representado por um objeto Index, para
que você possa alterar suas colunas em linhas e vice-versa transpondo seu DataFrame:
In [16]: df.T # Shortcut for df.transpose()
Out[16]: user_id 1001 1000 1002 1003
properties
name Mark John Tim Jenny
age 55 33 41 12
country Italy USA USA Germany
score 4.5 6.7 3.9 9
continent Europe America America Europe
Vale lembrar aqui que nosso DataFrame df ainda está inalterado, pois nunca reatribuímos
o DataFrame de retorno das chamadas de método de volta para a variável original df. Se
você quiser reordenar as colunas de um DataFrame, você pode usar o método reindex que
usamos com o índice, mas selecionar as colunas na ordem desejada geralmente é mais
intuitivo:
In [17]: df.loc[:, ["continent", "country", "name", "age", "score"]]
Out[17]: properties continent country name age score
user_id
1001 Europe Italy Mark 55 4.5
1000 America USA John 33 6.7
1002 America USA Tim 41 3.9
1003 Europe Germany Jenny 12 9.0
Este último exemplo precisa de algumas explicações: tudo sobre loc e como a seleção de
dados funciona é o tópico da próxima seção.
Manipulação de dados
Os dados do mundo real dificilmente são servidos em uma bandeja de prata, então antes de
trabalhar com eles, você precisa limpá-los e trazê-los para uma forma digerível.
Começaremos esta seção analisando como selecionar dados de um DataFrame, como alterá-
lo e como lidar com dados ausentes e duplicados. Em seguida, realizaremos alguns cálculos
com DataFrames e veremos como você trabalha com dados de texto. Para encerrar esta
seção, descobriremos quando o pandas retorna uma visualização versus uma cópia dos
dados. Alguns conceitos nesta seção estão relacionados ao que já vimos com arrays NumPy
no capítulo anterior.
Página 95 Capítulo 5
Selecionando dados
Vamos começar acessando dados por rótulo e posição antes de examinar outros métodos,
incluindo indexação booleana e seleção de dados usando um MultiIndex.
loc suporta a notação de fatia e, portanto, aceita dois pontos para selecionar todas as linhas
ou colunas, respectivamente. Além disso, você pode fornecer listas com rótulos, bem como
um único nome de coluna ou linha. Dê uma olhada na Tabela 5-1 para ver alguns exemplos
de como você seleciona diferentes partes do nosso exemplo DataFrame df.
Aplicando nosso conhecimento da Tabela 5-1, vamos usar loc para selecionar escalares,
séries e DataFrames:
In [18]: # Using scalars for both row and column selection returns a scalar
df.loc[1001, "name"]
Out[18]: 'Mark'
In [19]: # Using a scalar on either the row or column selection returns a Series
df.loc[[1001, 1002], "age"]
Página 96 Capítulo 5
Out[19]: user_id
1001 55
1002 41
Name: age, dtype: int64
In [20]: # Selecting multiple rows and columns returns a DataFrame
df.loc[:1002, ["name", "country"]]
Out[20]: properties name country
user_id
1001 Mark Italy
1000 John USA
1002 Tim USA
É importante que você entenda a diferença entre um DataFrame com uma ou mais colunas
e a Series: mesmo com uma única coluna, DataFrames são bidimensionais, enquanto Series
são unidimensionais. Tanto o DataFrame quanto o Series têm um índice, mas apenas o
DataFrame tem cabeçalhos de coluna. Quando você seleciona uma coluna como Série, o
cabeçalho da coluna se torna o nome da Série. Muitas funções ou métodos funcionarão em
Series e DataFrame, mas quando você executa cálculos aritméticos, o comportamento é
diferente: com DataFrames, pandas alinha os dados de acordo com os cabeçalhos das
colunas - mais sobre isso um pouco mais adiante neste capítulo.
Atalho para
colunas Como selecionar colunas é uma operação tão comum, pandas oferece um
atalho. Em vez de:
df.loc[:, column_selection]
você pode escrever:
df[column_selection]
Por exemplo, df["country"] retorna uma série do nosso DataFrame de exemplo
e df[["name", "country"]] retorna um DataFrame com duas colunas.
Ao usar fatias, você lida com os intervalos semiabertos padrão. A Tabela 5-2 fornece os
mesmos casos que analisamos anteriormente na Tabela 5-1.
Página 97 Capítulo 5
Tabela 5-2. Seleção de dados por posição
Seleção Retorno Tipo de dados Exemplo
Valor único Scalar df.iloc[1, 2]
Uma coluna (1d) Series df.iloc[:, 2]
Uma coluna (2d) DataFrame df.iloc[:, [2]]
Várias colunas DataFrame df.iloc[:, [2, 1]]
Faixa de colunas DataFrame df.iloc[:, :3]
Uma linha (1d) Series df.iloc[1, :]
Uma linha (2d) DataFrame df.iloc[[1], :]
Várias linhas DataFrame df.iloc[[3, 1], :]
Intervalo de linhas DataFrame df.iloc[1:3, :]
Aqui está como você usa iloc — novamente com as mesmas amostras que usamos com
loc antes:
In [21]: df.iloc[0, 0] # Returns a Scalar
Out[21]: 'Mark'
In [22]: df.iloc[[0, 2], 1] # Returns a Series
Out[22]: user_id
1001 55
1002 41
Name: age, dtype: int64
In [23]: df.iloc[:3, [0, 2]] # Returns a DataFrame
Out[23]: properties name country
user_id
1001 Mark Italy
1000 John USA
1002 Tim USA
Selecionar dados por rótulo ou posição não é o único meio de acessar um subconjunto de
seu Quadro de dados. Outra maneira importante é usar a indexação booleana; Vamos ver
como isso funciona!
Há duas coisas que preciso explicar aqui. Primeiro, devido a limitações técnicas, você não
pode usar os operadores booleanos do Python do Capítulo 3 com DataFrames. Em vez
disso, você precisa usar os símbolos conforme mostrado na Tabela 5-3.
Segundo, se você tiver mais de uma condição, certifique-se de colocar cada expressão
booleana entre parênteses para que a precedência do operador não atrapalhe: por exemplo,
& tem precedência do operador maior que ==. Portanto, sem parênteses, a expressão da
amostra seria interpretada como:
df["age"] > (40 & df["country"]) == "USA"
Para o que você usaria o operador com estruturas de dados Python básicas como listas, use
isin com um Series. É assim que você filtra seu DataFrame para participantes da Itália e
Alemanha:
In [27]: df.loc[df["country"].isin(["Italy", "Germany"]), :]
Out[27]: properties name age country score continent
user_id
Página 99 Capítulo 5
1001 Mark 55 Italy 4.5 Europe
1003 Jenny 12 Germany 9.0 Europe
Enquanto você usa loc para fornecer uma série booleana, DataFrames oferecem uma
sintaxe especial sem loc para selecionar valores dados o DataFrame completo de booleanos:
df[boolean_df]
Isso é especialmente útil se você tiver DataFrames que consistem apenas em números.
Fornecer um DataFrame de booleans retorna o DataFrame com NaN onde quer que o
DataFrame booleano seja False. Novamente, uma discussão mais detalhada de NaN seguirá
em breve. Vamos começar criando um novo DataFrame de amostra chamado rain que
consiste apenas em números:
In [28]: # This could be the yearly rainfall in millimeters
rainfall = pd.DataFrame(data={"City 1": [300.1, 100.2],
"City 2": [400.3, 300.4],
"City 3": [1000.5, 1100.6]})
rainfall
Out[28]: City 1 City 2 City 3
0 300.1 400.3 1000.5
1 100.2 300.4 1100.6
In [29]: rainfall < 400
Out[29]: City 1 City 2 City 3
0 True False False
1 True True False
In [30]: rainfall[rainfall < 400]
Out[30]: City 1 City 2 City 3
0 300.1 NaN NaN
1 100.2 300.4 NaN
Observe que neste exemplo, usei um dicionário para construir um novo DataFrame — isso
geralmente é conveniente se os dados já existirem nesse formato. Trabalhar com booleanos
dessa maneira é mais comumente usado para filtrar valores específicos, como valores
discrepantes.
Para encerrar a parte de seleção de dados, apresentarei um tipo especial de índice chamado
MultiIndex.
In [32]: df_multi.loc["Europe", :]
Out[32]: properties user_id name age score
country
Germany 1003 Jenny 12 9.0
Italy 1001 Mark 55 4.5
Observe que pandas embeleza a saída de um MultiIndex não repetindo o nível de índice
mais à esquerda (os continentes) para cada fila. Em vez disso, apenas imprime o continente
quando muda. A seleção em vários níveis de índice é feita fornecendo uma tupla:
In [33]: df_multi.loc[("Europe", "Italy"), :]
Out[33]: properties user_id name age score
continent country
Europe Italy 1001 Mark 55 4.5
Embora não criemos manualmente um MultiIndex neste livro, existem certas operações
como groupby, que farão com que os pandas retornem um DataFrame com um
MultiIndex, então é bom saber o que é. Encontraremos groupby mais adiante neste
capítulo.
Agora que você conhece várias maneiras de selecionar dados, é hora de aprender como você
altera os dados.
Configurando dados
A maneira mais fácil de alterar os dados de um DataFrame é atribuir valores a determinados
elementos usando os atributos loc ou iloc. Este é o ponto de partida desta seção antes de
nos voltarmos para outras formas de manipular DataFrames existentes: substituindo valores
e adicionando novas colunas.
Página 101 Capítulo 5
Definindo dados por rótulo ou posição
Como apontado anteriormente neste capítulo, quando você chama métodos DataFrame
como df.reset_index(), o método sempre será aplicado a uma cópia, deixando o
DataFrame original intocado. No entanto, atribuir valores por meio dos atributos loc e
iloc altera o DataFrame original. Como quero deixar nosso DataFrame df intocado, estou
trabalhando com uma cópia aqui que estou chamando df2. Se você quiser alterar um único
valor, faça o seguinte:
In [35]: # Copy the DataFrame first to leave the original untouched
df2 = df.copy()
In [36]: df2.loc[1000, "name"] = "JOHN"
df2
Out[36]: properties name age country score continent
user_id
1001 Mark 55 Italy 4.5 Europe
1000 JOHN 33 USA 6.7 America
1002 Tim 41 USA 3.9 America
1003 Jenny 12 Germany 9.0 Europe
Você também pode alterar vários valores ao mesmo tempo. Uma forma de alterar a
pontuação dos usuários com ID 1000 e 1001 é usar uma lista:
In [37]: df2.loc[[1000, 1001], "score"] = [3, 4]
df2
Out[37]: properties name age country score continent
user_id
1001 Mark 55 Italy 4.0 Europe
1000 JOHN 33 USA 3.0 America
1002 Tim 41 USA 3.9 America
1003 Jenny 12 Germany 9.0 Europe
Alterando dados por posição via funciona iloc da mesma maneira. Vamos agora ver como
você altera os dados usando a indexação booleana.
Às vezes, você tem um conjunto de dados em que precisa substituir determinados valores
em geral, ou seja, não específicos para determinadas colunas. Nesse caso, use a sintaxe
especial novamente e forneça todo o DataFrame com booleanos como este (o exemplo faz
uso novamente do DataFrame rainfall):
In [39]: # Copy the DataFrame first to leave the original untouched
rainfall2 = rainfall.copy()
rainfall2
Out[39]: City 1 City 2 City 3
0 300.1 400.3 1000.5
1 100.2 300.4 1100.6
In [40]: # Set the values to 0 wherever they are below 400
rainfall2[rainfall2 < 400] = 0
rainfall2
Out[40]: City 1 City 2 City 3
0 0.0 400.3 1000.5
1 0.0 0.0 1100.6
Se você deseja apenas substituir um valor por outro, existe uma maneira mais fácil de fazer
isso, como mostrarei a seguir.
Se, em vez disso, você só queria agir na coluna country, você poderia usar esta sintaxe:
df2.replace({"country": {"USA": "US"}})
Nesse caso, como USA só aparece na coluna country, ela produz o mesmo resultado da
amostra anterior. Para encerrar esta seção, vamos ver como você pode adicionar colunas
adicionais a um DataFrame.
Página 103 Capítulo 5
Configurando dados adicionando uma nova coluna
Para adicionar uma nova coluna a um DataFrame, atribua valores a um novo nome de
coluna. Por exemplo, você pode adicionar uma nova coluna a um DataFrame usando um
escalar ou lista:
In [42]: df2.loc[:, "discount"] = 0
df2.loc[:, "price"] = [49.9, 49.9, 99.9, 99.9]
df2
Out[42]: properties name age country score continent discount price
user_id
1001 Mark 55 Italy 4.0 Europe 0 49.9
1000 xxx 33 USA 3.0 America 0 49.9
1002 xxx 41 USA 3.9 America 0 99.9
1003 xxx 12 Germany 9.0 Europe 0 99.9
Vou mostrar mais sobre cálculo com DataFrames em breve, mas antes de chegarmos lá, você
lembra que eu já usei NaN algumas vezes? A próxima seção finalmente fornecerá mais
contexto sobre o tópico de dados ausentes.
Dados Ausentes
Os dados ausentes podem ser um problema, pois têm o potencial de influenciar os
resultados de sua análise de dados, tornando suas conclusões menos robustas. No entanto,
é muito comum haver lacunas em seus conjuntos de dados com as quais você terá que lidar.
No Excel, você geralmente tem que lidar com células vazias ou erros #N/A, mas pandas usa
np.nan para dados ausentes, exibidos como NaN. NaN é o padrão de ponto flutuante para
Not-a-Number. Para carimbos de data/hora, pd.NaT é usado e, para texto, pandas usa
None. Usando None ou np.nan, você pode introduzir valores ausentes:
In [44]: df2 = df.copy() # Let's start with a fresh copy
df2.loc[1000, "score"] = None
df2.loc[1003, :] = None
df2
Out[44]: properties name age country score continent
user_id
1001 Mark 55.0 Italy 4.5 Europe
1000 John 33.0 USA NaN America
Página 104 Capítulo 5
1002 Tim 41.0 USA 3.9 America
1003 None NaN None NaN None
Para limpar um DataFrame, muitas vezes você deseja remover linhas com dados ausentes.
Isso é tão simples quanto:
In [45]: df2.dropna()
Out[45]: properties name age country score continent
user_id
1001 Mark 55.0 Italy 4.5 Europe
1002 Tim 41.0 USA 3.9 America
Se, no entanto, você deseja apenas remover linhas onde todos os valores estão faltando, use o
parâmetro how:
In [46]: df2.dropna(how="all")
Out[46]: properties name age country score continent
user_id
1001 Mark 55.0 Italy 4.5 Europe
1000 John 33.0 USA NaN America
1002 Tim 41.0 USA 3.9 America
Para obter um DataFrame ou Series booleano dependendo se existe NaN ou não, use isna:
In [47]: df2.isna()
Out[47]: properties name age country score continent
user_id
1001 False False False False False
1000 False False False True False
1002 False False False False False
1003 True True True True True
Para preencher valores ausentes, use fillna. Por exemplo, para substituir NaN na coluna de
pontuação por sua média (apresentarei estatísticas descritivas como mean breve):
In [48]: df2.fillna({"score": df2["score"].mean()})
Out[48]: properties name age country score continent
user_id
1001 Mark 55.0 Italy 4.5 Europe
1000 John 33.0 USA 4.2 America
1002 Tim 41.0 USA 3.9 America
1003 None NaN None 4.2 None
Dados ausentes não são a única condição que exige que limpemos nosso conjunto de dados.
O mesmo vale para dados duplicados, então vamos ver quais são nossas opções!
Página 105 Capítulo 5
Dados duplicados
Assim como os dados ausentes, as duplicatas afetam negativamente a confiabilidade de sua
análise. Para se livrar de linhas duplicadas, use o método drop_duplicates.
Opcionalmente, você pode fornecer um subconjunto das colunas como argumento:
In [49]: df.drop_duplicates(["country", "continent"])
Out[49]: properties name age country score continent
user_id
1001 Mark 55 Italy 4.5 Europe
1000 John 33 USA 6.7 America
1003 Jenny 12 Germany 9.0 Europe
Por padrão, isso deixará o primeiro ocorrência. Para descobrir se uma determinada coluna
contém duplicatas ou para obter seus valores exclusivos, use os dois comandos a seguir (use
df.index em vez de df["country"] se você quiser executar isso no índice):
In [50]: df["country"].is_unique
Out[50]: False
In [51]: df["country"].unique()
Out[51]: array(['Italy', 'USA', 'Germany'], dtype=object)
E por fim, para entender quais linhas são duplicadas, utilize o método duplicated, que
retorna um booleano Series: by padrão, ele usa o parâmetro keep="first", que mantém a
primeira ocorrência e marca apenas as duplicatas com True. Ao definir o parâmetro
keep=False, ele retornará True para todas as linhas, incluindo sua primeira ocorrência,
facilitando a obtenção de um DataFrame com todas as linhas duplicadas. No exemplo a
seguir, examinamos a coluna do country em busca de duplicatas, mas, na realidade, você
geralmente observa o índice ou linhas inteiras. Neste caso, você teria que usar
df.index.duplicated() ou df.duplicated() em vez disso:
In [52]: # By default, it marks only duplicates as True, i.e.
# without the first occurrence
df["country"].duplicated()
Out[52]: user_id
1001 False
1000 False
1002 True
1003 False
Name: country, dtype: bool
In [53]: # To get all rows where "country" is duplicated, use
# keep=False
df.loc[df["country"].duplicated(keep=False), :]
Out[53]: properties name age country score continent
user_id
Página 106 Capítulo 5
1000 John 33 USA 6.7 America
1002 Tim 41 USA 3.9 America
Depois de limpar seus DataFrames removendo dados ausentes e duplicados, você pode
querer realizar algumas operações aritméticas — a próxima seção fornece uma introdução
de como isso funciona.
Operações aritméticas
Assim como os arrays NumPy, DataFrames e Series fazem uso de vetorização. Por exemplo,
para adicionar um número a cada valor no DataFrame rainfall, basta fazer o seguinte:
In [54]: rainfall
Out[54]: City 1 City 2 City 3
0 300.1 400.3 1000.5
1 100.2 300.4 1100.6
In [55]: rainfall + 100
Out[55]: City 1 City 2 City 3
0 400.1 500.3 1100.5
1 200.2 400.4 1200.6
O índice e as colunas do DataFrame resultante são os união dos índices e colunas dos dois
DataFrames: os campos que possuem um valor em ambos os DataFrames mostram a soma,
enquanto o restante do DataFrame mostra NaN. Isso pode ser algo com o qual você precisa
se acostumar se vier do Excel, onde células vazias são automaticamente transformadas em
zeros quando você as usa em operações aritméticas. Para obter o mesmo comportamento
do Excel, use o método add com um fill_value para substituir os valores NaN por zeros:
In [58]: rainfall.add(more_rainfall, fill_value=0)
Página 107 Capítulo 5
Out[58]: City 1 City 2 City 3 City 4
0 300.1 400.3 1000.5 NaN
1 200.2 300.4 1100.6 200.0
2 300.0 NaN NaN 400.0
Quando você tem um DataFrame e uma Série em seu cálculo, por padrão a Série é
transmitida junto ao índice:
In [59]: # A Series taken from a row
rainfall.loc[1, :]
Out[59]: City 1 100.2
City 2 300.4
City 3 1100.6
Name: 1, dtype: float64
In [60]: rainfall + rainfall.loc[1, :]
Out[60]: City 1 City 2 City 3
0 400.3 700.7 2101.1
1 200.4 600.8 2201.2
Portanto, para adicionar uma Série em coluna, você precisa usar o método add de argumento
explícito axis:
In [61]: # A Series taken from a column
rainfall.loc[:, "City 2"]
Out[61]: 0 400.3
1 300.4
Name: City 2, dtype: float64
In [62]: rainfall.add(rainfall.loc[:, "City 2"], axis=0)
Out[62]: City 1 City 2 City 3
0 700.4 800.6 1400.8
1 400.6 600.8 1401.0
Enquanto esta seção é sobre DataFrames com números e como eles se comportam em
operações aritméticas, a próxima seção mostra suas opções quando se trata de manipular
texto em DataFrames.
Página 108 Capítulo 5
Trabalhando com Colunas de Texto
Como vimos no início deste capítulo, colunas com texto ou tipos de dados mistos possuem
o tipo de dados object. Para realizar operações em colunas com strings de texto, use o
atributo str que dá acesso aos métodos string do Python. Já conhecemos alguns métodos
de string no Capítulo 3, mas não custa dar uma olhada nos métodos disponíveis na
documentação do Python. Por exemplo, para remover espaços em branco à esquerda e à
direita, use o método strip; para tornar todas as primeiras letras maiúsculas, existe o
método capitalize. Encadeando-os juntos, limpará as colunas de texto confusas que
geralmente são o resultado da entrada manual de dados:
In [63]: # Let's create a new DataFrame
users = pd.DataFrame(data=[" mArk ", "JOHN ", "Tim", " jenny"],
columns=["name"])
users
Out[63]: name
0 mArk
1 JOHN
2 Tim
3 jenny
In [64]: users_cleaned = users.loc[:, "name"].str.strip().str.capitalize()
users_cleaned
Out[64]: 0 Mark
1 John
2 Tim
3 Jenny
Name: name, dtype: object
Os métodos de string são fáceis de usar, mas às vezes você pode precisar manipular um
DataFrame de uma maneira que não esteja incorporada . Nesse caso, crie sua própria função
e aplique-a ao seu DataFrame, como mostra a próxima seção.
Para dividir isso: a seguinte f-string retorna x como uma string: f"{x}". Para adicionar
formatação, acrescente dois pontos à variável seguido pela string de formatação ,.2f. A
vírgula é o separador de milhares e .2f significa notação de ponto fixo com dois dígitos
após o ponto decimal. Para obter mais detalhes sobre como formatar strings, consulte a
Format Specification Mini-Language, que faz parte da documentação do Python.
Para este tipo de caso de uso, as expressões lambda (veja a barra lateral) são amplamente
usadas, pois permitem que você escreva o mesmo em uma única linha sem precisar definir
uma função separada. Com expressões lambda, podemos reescrever o exemplo anterior da
seguinte forma:
In [69]: rainfall.applymap(lambda x: f"{x:,.2f}")
Out[69]: City 1 City 2 City 3
0 300.10 400.30 1,000.50
1 100.20 300.40 1,100.60
Lambda Expressions
Python permite que você defina uma função em uma única linha via lambda expressões.
As expressões lambda são funções anônimas, o que significa que é uma função sem nome.
Considere esta função:
def function_name(arg1, arg2, ...):
return return_value
Esta função pode ser reescrita como uma expressão lambda como esta:
lambda arg1, arg2, ...: return_value
Em essência, você substitui def por lambda, leave retire a palavra-chave return e o nome
da função, e coloque tudo em uma linha. Como vimos com o método applymap, isso pode
ser muito conveniente neste caso, pois não precisamos definir uma função para algo que está
sendo usado apenas uma vez.
Página 110 Capítulo 5
Já mencionei todos os métodos importantes de manipulação de dados, mas antes de
prosseguirmos, é importante entender quando o pandas usa uma visualização de um
DataFrame e quando usa uma cópia.
Embora as coisas sejam complicadas com loc e iloc, vale lembrar que todos os métodos
DataFrame como df.dropna() ou df.sort_values("column_name") sempre retornam
uma cópia.
Até agora, trabalhamos principalmente com um DataFrame por vez. A próxima seção
mostra várias maneiras de combinar vários DataFrames em um, uma tarefa muito comum
para a qual o pandas oferece ferramentas poderosas.
Combinando DataFrames
Combinar diferentes conjuntos de dados no Excel pode ser uma tarefa complicada e
normalmente envolve muitas fórmulas PROCV. Felizmente, combinar DataFrames é um dos
recursos matadores dos pandas, onde seus recursos de alinhamento de dados facilitarão sua
vida, reduzindo bastante a possibilidade de introduzir erros. Combinar e mesclar
DataFrames pode ser feito de várias maneiras; esta seção examina apenas os casos mais
comuns usando concat, join e merge. Embora tenham uma sobreposição, cada função
torna uma tarefa específica muito simples. Vou começar com a função concat, depois
explicar as diferentes opções com join e concluir apresentando merge, a função mais
genérica das três.
Página 111 Capítulo 5
Concatenando
Para simplesmente colar vários DataFrames juntos, a função concat é sua melhor amiga.
Como você pode perceber pelo nome da função, esse processo tem o nome técnico
concatenação. Por padrão, concat cola DataFrames ao longo das linhas e alinha as colunas
automaticamente. No exemplo a seguir, crio outro DataFrame, more_users, e o anexei à
parte inferior do nosso exemplo DataFrame df:
In [70]: data=[[15, "France", 4.1, "Becky"],
[44, "Canada", 6.1, "Leanne"]]
more_users = pd.DataFrame(data=data,
columns=["age", "country", "score", "name"],
index=[1000, 1011])
more_users
Out[70]: age country score name
1000 15 France 4.1 Becky
1011 44 Canada 6.1 Leanne
In [71]: pd.concat([df, more_users], axis=0)
Out[71]: name age country score continent
1001 Mark 55 Italy 4.5 Europe
1000 John 33 USA 6.7 America
1002 Tim 41 USA 3.9 America
1003 Jenny 12 Germany 9.0 Europe
1000 Becky 15 France 4.1 NaN
1011 Leanne 44 Canada 6.1 NaN
Observe que agora você tem elementos de índice duplicados, pois concat cola os dados no
eixo indicado (linhas) e apenas alinha os dados no outro (colunas), combinando assim os
nomes das colunas automaticamente — mesmo que não estejam na mesma ordem nos dois
DataFrames! Se você quiser colar dois DataFrames juntos ao longo das colunas, defina
axis=1:
In [72]: data=[[3, 4],
[5, 6]]
more_categories = pd.DataFrame(data=data,
columns=["quizzes", "logins"],
index=[1000, 2000])
more_categories
Out[72]: quizzes logins
1000 3 4
2000 5 6
In [73]: pd.concat([df, more_categories], axis=1)
Out[73]: name age country score continent quizzes logins
1000 John 33.0 USA 6.7 America 3.0 4.0
1001 Mark 55.0 Italy 4.5 Europe NaN NaN
1002 Tim 41.0 USA 3.9 America NaN NaN
Página 112 Capítulo 5
1003 Jenny 12.0 Germany 9.0 Europe NaN NaN
2000 NaN NaN NaN NaN NaN 5.0 6.0
O recurso especial e muito útil do concat é que ele aceita mais de dois DataFrames.
Usaremos isso no próximo capítulo para criar um único DataFrame de vários arquivos CSV:
pd.concat([df1, df2, df3, ...])
Por outro lado, join e merge só funcionam com dois DataFrames, como veremos a seguir.
Unindo e Mesclando
Ao unir dois DataFrames, você combina as colunas de cada DataFrame em um novo
DataFrame enquanto decide o que acontece com as linhas com base na teoria dos conjuntos.
Se você já trabalhou com bancos de dados relacionais antes, é o mesmo conceito da cláusula
JOIN em consultas SQL. Figura 5-3 mostra como os quatro tipos de junção (que é a junção
interna, esquerda, direita e externa) funcionam usando dois DataFrames de amostra, df1 e
df2.
Com join, pandas usa os índices de ambos os DataFrames para alinhar as linhas. Uma
junção interna retorna um DataFrame apenas com as linhas em que os índices se
sobrepõem. Uma junção à esquerda pega todas as linhas do DataFrame df1 e corresponde
às linhas do DataFrame df2 no índice. Onde df2 não tiver uma linha correspondente, os
pandas preencherão NaN. A junção esquerda corresponde ao caso PROCV no Excel. A junção
direita pega todas as linhas da tabela direita df2 e as combina com as linhas de df1 no
índice. E, finalmente, o outer join, que é a abreviação de full outer join, usa a união de
índices de ambos os DataFrames e corresponde aos valores onde puder. Tabela 5-5 é o
equivalente da Figura 5-3 em forma de texto.
Página 113 Capítulo 5
Tabela 5-5. Tipos
Tipo Descrição
Inner Only linhas cujo índice existe em ambos os DataFrames
Left Todas as linhas do DataFrame esquerdo, combinando linhas do DataFrame
Right Todas as linhas do DataFrame direito, combinando linhas do DataFrame
Outer A união de índices de linha de ambos os DataFrames
Vamos ver como isso funciona na prática, dando vida aos exemplos da Figura 5-3 :
In [74]: df1 = pd.DataFrame(data=[[1, 2], [3, 4], [5, 6]],
columns=["A", "B"])
df1
Out[74]: A B
0 1 2
1 3 4
2 5 6
In [75]: df2 = pd.DataFrame(data=[[10, 20], [30, 40]],
columns=["C", "D"], index=[1, 3])
df2
Out[75]: C D
1 10 20
3 30 40
In [76]: df1.join(df2, how="inner")
Out[76]: A B C D
1 3 4 10 20
In [77]: df1.join(df2, how="left")
Out[77]: A B C D
0 1 2 NaN NaN
1 3 4 10.0 20.0
2 5 6 NaN NaN
In [78]: df1.join(df2, how="right")
Out[78]: A B C
D 1 3.0 4.0 10
20
3 NaN NaN 30 40
In [79]: df1.join(df2, how="outer")
Out[79]: A B C D
0 1.0 2.0 NaN NaN
1 3.0 4.0 10.0 20.0
2 5.0 6.0 NaN NaN
3 NaN NaN 30.0 40.0
Se você deseja unir em uma ou mais colunas DataFrame em vez de confiar no índice, use
merge em vez de join. merge aceita o argumento on para fornecer um ou
Página 114 Capítulo 5
mais colunas como condição de junção: essas colunas, que devem existir em ambos os
DataFrames, são usadas para corresponder às linhas:
In [80]: # Add a column called "category" to both DataFrames
df1["category"] = ["a", "b", "c"]
df2["category"] = ["c", "b"]
In [81]: df1
Out[81]: A B category
0 1 2 a
1 3 4 b
2 5 6 c
In [82]: df2
Out[82]: C D category
1 10 20 c
3 30 40 b
In [83]: df1.merge(df2, how="inner", on=["category"])
Out[83]: A B category C D
0 3 4 b 30 40
1 5 6 c 10 20
In [84]: df1.merge(df2, how="left", on=["category"])
Out[84]: A B category C D
0 1 2 a NaN NaN
1 3 4 b 30.0 40.0
2 5 6 c 10.0 20.0
Desde join e merge aceitar alguns argumentos opcionais para acomodar cenários mais
complexos, convido você a dar uma olhada na documentação oficial para saber mais sobre
eles.
Agora você sabe como manipular um ou mais DataFrames, o que nos leva ao próximo passo
em nossa jornada de análise de dados: dar sentido aos dados.
Estatísticas descritivas
As estatísticas descritivas permitem resumir conjuntos de dados usando medidas
quantitativas. Por exemplo, o número de pontos de dados é uma estatística descritiva
simples. Médias como média, mediana ou moda são outros exemplos populares.
Página 115 Capítulo 5
DataFrames e Series permitem que você acesse estatísticas descritivas convenientemente por
meio de métodos como sum, mean e count, para citar apenas alguns. Você conhecerá muitos
deles ao longo deste livro, e a lista completa está disponível na documentação do pandas.
Por padrão, eles retornam uma série ao longo de axis=0, o que significa que você obtém a
estatística das colunas:
In [85]: rainfall
Out[85]: City 1 City 2 City 3
0 300.1 400.3 1000.5
1 100.2 300.4 1100.6
In [86]: rainfall.mean()
Out[86]: City 1 200.15
City 2 350.35
City 3 1050.55
dtype: float64
Por padrão, os valores ausentes não são incluídos nas estatísticas descritivas como sum ou
mean. Isso está de acordo com a forma como o Excel trata células vazias, portanto, usar a
fórmula AVERAGE em um intervalo com células vazias fornecerá o mesmo resultado que o
método mean aplicado em uma série com os mesmos números e NaN em vez de células
vazias.
Obter uma estatística em todas as linhas de um DataFrame às vezes não é bom o suficiente
e você precisa de informações mais granulares – a média por categoria, por exemplo. Vamos
ver como é feito!
Agrupando
Usando nosso exemplo DataFrame df novamente, vamos descobrir a pontuação média por
continente! Para isso, primeiro você agrupa as linhas por continente e posteriormente aplica
o método mean, que calculará a média por grupo. Todas as colunas não numéricas são
excluídas automaticamente:
In [88]: df.groupby(["continent"]).mean()
Out[88]: properties age score
continent
America 37.0 5.30
Europe 33.5 6.75
Se você incluir mais de uma coluna, o DataFrame resultante terá um índice hierárquico — o
MultiIndex que conhecemos anteriormente:
In [89]: df.groupby(["continent", "country"]).mean()
Página 116 Capítulo 5
Out[89]: properties age score
continent country
America USA 37 5.3
Europe Germany 12 9.0
Italy 55 4.5
Em vez de mean, você pode usar a maioria das estatísticas descritivas que o pandas oferece e
se quiser usar sua própria função, use o método agg. Por exemplo, aqui está como você
obtém a diferença entre o valor máximo e mínimo por grupo:
In [90]: df.groupby(["continent"]).agg(lambda x: x.max() - x.min())
Out[90]: properties age score
continent
America 8 2.8
Europe 43 4.5
Uma maneira popular de obter estatísticas por grupo no Excel é usar tabelas dinâmicas. Eles
introduzem uma segunda dimensão e são ótimos para analisar seus dados de diferentes
perspectivas. pandas também tem uma funcionalidade de tabela dinâmica, como veremos a
seguir.
Pivoting e Melting
Se você estiver usando tabelas dinâmicas no Excel, não terá problemas para aplicar a função
pivot_table, pois ela funciona basicamente da mesma maneira. Os dados no DataFrame a
seguir são organizados de maneira semelhante à forma como os registros são normalmente
armazenados em um banco de dados; cada linha mostra uma transação de venda de uma
fruta específica em uma determinada região:
In [91]: data = [["Oranges", "North", 12.30],
["Apples", "South", 10.55],
["Oranges", "South", 22.00],
["Bananas", "South", 5.90],
["Bananas", "North", 31.30],
["Oranges", "North", 13.10]]
sales = pd.DataFrame(data=data,
columns=["Fruit", "Region", "Revenue"])
sales
Out[91]: Fruit Region Revenue
0 Oranges North 12.30
1 Apples South 10.55
2 Oranges South 22.00
3 Bananas South 5.90
4 Bananas North 31.30
5 Oranges North 13.10
Para criar uma tabela dinâmica, você fornece o DataFrame como o primeiro argumento para
a função pivot_table. index e columns definem qual coluna do DataFrame se tornará
os rótulos de linha e coluna da tabela dinâmica, respectivamente. values serão agregados
na parte de dados do DataFrame resultante usando o aggfunc, uma
Página 117 Capítulo 5
função que pode ser fornecida como uma string ou NumPy ufunc. E finalmente, margins
Corresponde a Grand Total no Excel, ou seja, se você deixar margins e margins_name
longe, a coluna Total e a linha não será mostrada:
In [92]: pivot = pd.pivot_table(sales,
index="Fruit", columns="Region",
values="Revenue", aggfunc="sum",
margins=True, margins_name="Total")
pivot
Out[92]: Region North South Total
Fruit
Apples NaN 10.55 10.55
Bananas 31.3 5.90 37.20
Oranges 25.4 22.00 47.40
Total 56.7 38.45 95.15
Em resumo, dinamizar seus dados significa pegar os valores exclusivos de uma coluna
(Region no nosso caso) e transformá-los nos cabeçalhos de coluna da tabela dinâmica,
agregando assim os valores de outra coluna. Isso facilita a leitura de informações resumidas
nas dimensões de interesse. Na nossa tabela dinâmica, você vê instantaneamente que não
houve venda de maçãs na região norte e que na região sul a maior parte da receita vem da
laranja. Se você quiser fazer o contrário e transformar os cabeçalhos das colunas nos valores
de uma única coluna, use melt. Nesse sentido, melt é o oposto da função pivot_table:
In [93]: pd.melt(pivot.iloc[:-1,:-1].reset_index(),
id_vars="Fruit",
value_vars=["North", "South"], value_name="Revenue")
Out[93]: Fruit Region Revenue
0 Apples North NaN
1 Bananas North 31.30
2 Oranges North 25.40
3 Apples South 10.55
4 Bananas South 5.90
5 Oranges South 22.00
Aqui, estou fornecendo nossa tabela dinâmica como entrada, mas estou usando iloc para
eliminar o total de linhas e colunas. Eu também defini o índice para que todas as
informações estejam disponíveis como colunas regulares. Em seguida, forneço id_vars
para indicar os identificadores e value_vars para definir quais colunas quero “desativar”.
A fusão pode ser útil se você quiser preparar os dados para que possam ser armazenados de
volta em um banco de dados que os espera nesse formato.
Trabalhar com estatísticas agregadas ajuda você a entender seus dados, mas ninguém gosta
de ler uma página cheia de números. Para tornar as informações facilmente compreensíveis,
nada funciona melhor do que criar visualizações, que é nosso próximo tópico. Enquanto o
Excel usa o termo gráficos, os pandas geralmente se referem a eles como gráficos. Usarei
esses termos de forma intercambiável neste livro.
Página 118 Capítulo 5
Plotagem
A plotagem permite que você visualize os resultados de sua análise de dados e pode ser a
etapa mais importante em todo o processo. Para plotagem, usaremos duas bibliotecas:
começamos examinando o Matplotlib, a biblioteca de plotagem padrão dos pandas, antes
de nos concentrarmos na Plotly, uma biblioteca de plotagem moderna que nos dá uma
experiência mais interativa em notebooks Jupyter.
Matplotlib
Matplotlib é um pacote de plotagem que existe há muito tempo e está incluído na
distribuição do Anaconda. Com ele, você pode gerar plotagens em diversos formatos,
incluindo gráficos vetoriais para impressão de alta qualidade. Quando você chama o método
plot de um DataFrame, os pandas produzirão um gráfico Matplotlib por padrão.
Para usar o Matplotlib em um notebook Jupyter, você precisa primeiro executar um dos
dois comandos mágicos (consulte a barra lateral “Comandos mágicos” na página 116):
%matplotlib inline ou %matplotlib notebook. Eles configuram o notebook para que
os gráficos possam ser exibidos no próprio notebook. O último comando adiciona um
pouco mais de interatividade, permitindo alterar o tamanho ou o fator de zoom do gráfico.
Vamos começar e criar um primeiro gráfico com pandas e Matplotlib (veja a Figura 5-4):
In [94]: import numpy as np
%matplotlib inline
# Or %matplotlib notebook
In [95]: data = pd.DataFrame(data=np.random.rand(4, 4) * 100000,
index=["Q1", "Q2", "Q3", "Q4"],
columns=["East", "West", "North", "South"])
data.index.name = "Quarters"
data.columns.name = "Region"
data
Out[95]: Region East West North South
Quarters
Q1 23254.220271 96398.309860 16845.951895 41671.684909
Q2 87316.022433 45183.397951 15460.819455 50951.465770
Q3 51458.760432 3821.139360 77793.393899 98915.952421
Q4 64933.848496 7600.277035 55001.831706 86248.512650
Observe que neste exemplo, usei um array NumPy para construir um DataFrame pandas.
Fornecer arrays NumPy permite que você aproveite os construtores do NumPy que
conhecemos no capítulo anterior; aqui, usamos NumPy para gerar um DataFrame de
pandas com base em números pseudoaleatórios. Portanto, ao executar a amostra do seu
lado, você obterá valores diferentes.
Comandos mágicos
O comando %matplotlib inline que usamos para fazer o Matplotlib funcionar
corretamente com notebooks Jupyter é um comando mágico. Comandos mágicos são um
conjunto de comandos simples que fazem com que uma célula do notebook Jupyter se
comporte de uma certa maneira ou torne tarefas complicadas tão fáceis que quase parece
mágica. Você escreve comandos mágicos em células como código Python, mas eles
começam com %% ou %. Os comandos que afetam a célula inteira começam com %% e os
comandos que afetam apenas uma única linha em uma célula começam com %.
Veremos mais comandos mágicos nos próximos capítulos, mas se você quiser listar todos os
comandos mágicos atualmente disponíveis, execute %lsmagic e, para uma descrição
detalhada, execute %magic.
Depois de executar a seguinte célula, o backend de plotagem de todo o notebook será ser
definido como Plotly e se você executar novamente a célula anterior, ela também será
renderizada como um gráfico Plotly. Para Plotly, em vez de executar um comando mágico,
você só precisa configurá-lo como backend antes de poder plotar Figuras 5-5 e 5-6:
In [97]: # Set the plotting backend to Plotly
pd.options.plotting.backend = "plotly"
In [98]: data.plot()
Criaremos mais gráficos no próximo capítulo para analisar séries temporais, mas antes de
chegarmos lá, vamos encerrar este capítulo aprendendo como podemos importar e exportar
dados com pandas!
Agora que você já sabe como usar o método df.to_csv, vamos ver como funciona a
importação de um arquivo CSV!
Muitas vezes, você precisará fornecer alguns parâmetros a mais para read_csv do que
apenas o nome do arquivo. Por exemplo, sep permite que você diga aos pandas qual
separador ou delimitador o arquivo CSV usa caso não seja a vírgula padrão. Usaremos mais
alguns parâmetros no próximo capítulo, mas para uma visão geral completa, dê uma olhada
na documentação do pandas.
Agora que estamos lidando com grandes DataFrames com muitas milhares de linhas,
normalmente a primeira coisa é executar o info para obter um resumo do DataFrame. Em
seguida, você pode dar uma olhada nas primeiras e últimas linhas do DataFrame usando os
head e tail . Esses métodos retornam cinco linhas por padrão, mas isso pode ser alterado
fornecendo o número desejado de linhas como argumento. Você também pode executar o
método describe para obter algumas estatísticas básicas:
In [102]: msft.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8622 entries, 0 to 8621
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Date 8622 non-null object
1 Open 8622 non-null float64
2 High 8622 non-null float64
3 Low 8622 non-null float64
4 Close 8622 non-null float64
5 Adj Close 8622 non-null float64
6 Volume 8622 non-null int64
dtypes: float64(5), int64(1), object(1)
memory usage: 471.6+ KB
In [103]: # Estou selecionando algumas colunas por causa de problemas de espaço
# Você também pode simplesmente executar: msft.head()
msft.loc[:, ["Date", "Adj Close", "Volume"]].head()
Página 125 Capítulo 5
Out[103]: Date Adj Close Volume
0 1986-03-13 0.062205 1031788800
1 1986-03-14 0.064427 308160000
2 1986-03-17 0.065537 133171200
3 1986-03-18 0.063871 67766400
4 1986-03-19 0.062760 47894400
In [104]: msft.loc[:, ["Date", "Adj Close", "Volume"]].tail(2)
Out[104]: Date Adj Close Volume
8620 2020-05-26 181.570007 36073600
8621 2020-05-27 181.809998 39492600
In [105]: msft.loc[:, ["Adj Close", "Volume"]].describe()
Out[105]: Adj Close Volume
count 8622.000000 8.622000e+03
mean 24.921952 6.030722e+07
std 31.838096 3.877805e+07
min 0.057762 2.304000e+06
25% 2.247503 3.651632e+07
50% 18.454313 5.350380e+07
75% 25.699224 7.397560e+07
max 187.663330 1.031789e+09
Adj Close significa preço de fechamento ajustado e corrige o preço das ações para ações
corporativas, como desdobramentos de ações. Volume é o número de ações que foram
negociadas. Eu resumi os vários métodos de exploração do DataFrame que vimos neste
capítulo na Tabela 5-8.
A função read_csv também aceita uma URL em vez de um arquivo CSV local. É assim que
você lê o arquivo CSV diretamente do repositório complementar:
In [106]: # The line break in the URL is only to make it fit on the page
url = ("https://raw.githubusercontent.com/fzumstein/"
"python-for-excel/1st-edition/csv/MSFT.csv")
msft = pd.read_csv(url)
In [107]: msft.loc[:, ["Date", "Adj Close", "Volume"]].head(2)
Out[107]: Date Adj Close Volume
0 1986-03-13 0.062205 1031788800
1 1986-03-14 0.064427 308160000
Página 126 Capítulo 5
Continuaremos com este conjunto de dados e a função read_csv no próximo capítulo
sobre séries temporais, onde transformará a coluna Date em um DatetimeIndex.
Conclusão
Este capítulo foi repleto de novos conceitos e ferramentas para analisar conjuntos de dados
em pandas. Aprendemos como carregar arquivos CSV, como lidar com dados ausentes ou
duplicados e como usar estatísticas descritivas. Também vimos como é fácil transformar
seus DataFrames em gráficos interativos. Embora possa demorar um pouco para digerir
tudo, provavelmente não demorará muito para que você entenda o imenso poder que está
ganhando ao adicionar pandas ao seu cinto de ferramentas. Ao longo do caminho,
comparamos os pandas com a seguinte funcionalidade do Excel: Funcionalidade
AutoFiltro
Consulte “Selecionando por indexação booleana” na página 94.
formula PROCV
Consulte “Unindo e Mesclando” na página 109.
Tabela Dinâmica
Consulte “Pivotar e derreter” na página 113.
Power Query
Esta é uma combinação de "Importar e exportar DataFrames" na página 119,
"Manipulação de dados" na página 91 e "Combinar DataFrames" na página 107.
O próximo capítulo trata da análise de séries temporais, a funcionalidade que levou à ampla
adoção de pandas pelo setor financeiro. Vamos ver por que essa parte dos pandas tem tanta
vantagem sobre o Excel!
Página 127
CAPÍTULO 6
Análise de séries temporais com
pandas
Uma série temporal é uma série de pontos de dados ao longo de um eixo baseado no tempo
que desempenha um papel central em muitos cenários diferentes: enquanto os comerciantes
usam os preços históricos das ações para calcular as medidas de risco, a previsão do tempo é
baseada no tempo série gerada por sensores que medem temperatura, umidade e pressão do
ar. E o departamento de marketing digital conta com séries temporais geradas por páginas
da web, por exemplo, a fonte e o número de visualizações de página por hora, e as usará para
tirar conclusões sobre suas campanhas de marketing.
A análise de séries temporais é uma das principais forças motrizes por que os cientistas e
analistas de dados começaram a procurar uma alternativa melhor ao Excel. Os pontos a
seguir resumem alguns dos motivos por trás dessa mudança:
Grandes conjuntos de dados
As séries temporais podem crescer rapidamente além do limite do Excel de
aproximadamente um milhão de linhas por planilha. Por exemplo, se você trabalha com
preços de ações intradiários em um nível de dados de ticks, geralmente está lidando com
centenas de milhares de registros — por ação e por dia!
Data e hora
Como vimos no Capítulo 3, o Excel tem várias limitações quando se trata de lidar com
data e hora, a espinha dorsal das séries temporais. Falta suporte para fusos horários e um
formato de número limitado a milissegundos são alguns deles. pandas suporta fusos
horários e usa datetime64[ns], que oferece uma resolução em até nanossegundos.
Falta de funcionalidade
O Excel perde até mesmo as ferramentas básicas para poder trabalhar com dados de
séries temporais de maneira decente. Por exemplo, se você deseja transformar uma série
temporal diária em uma série temporal mensal, não há uma maneira fácil de fazer isso,
apesar de ser uma tarefa muito comum.
Página 128 Capítulo 6
DataFrames permitem trabalhar com vários índices baseados em tempo: DatetimeIndex é
o mais comum e representa um índice com timestamps. Outros tipos de índice, como
PeriodIndex, são baseados em intervalos de tempo, como horas ou meses. Neste capítulo,
no entanto, estamos olhando apenas para DatetimeIndex, que apresentarei agora com mais
detalhes.
DatetimeIndex
Nesta seção, aprenderemos como construir um DatetimeIndex, como filtrar esse índice
para um intervalo de tempo específico e como trabalhar com fusos horários.
Criando um DatetimeIndex
Para construir um DatetimeIndex, pandas oferece a função date_range. Ele aceita uma
data de início, uma frequência e o número de períodos ou a data de término:
In [1]: # Let's start by importing the packages we use in this chapter
# and by setting the plotting backend to Plotly
import pandas as pd
import numpy as np
pd.options.plotting.backend = "plotly"
In [2]: # This creates a DatetimeIndex based on a start timestamp,
# number of periods and frequency ("D" = daily).
daily_index = pd.date_range("2020-02-28", periods=4, freq="D")
daily_index
Out[2]: DatetimeIndex(['2020-02-28', '2020-02-29', '2020-03-01', '2020-03-02'],
dtype='datetime64[ns]', freq='D')
In [3]: # This creates a DatetimeIndex based on start/end timestamp.
# The frequency is set to "weekly on Sundays" ("W-SUN").
weekly_index = pd.date_range("2020-01-01", "2020-01-31", freq="W-SUN")
weekly_index
Out[3]: DatetimeIndex(['2020-01-05', '2020-01-12', '2020-01-19', '2020-01-26'],
dtype='datetime64[ns]', freq='W-SUN')
In [4]: # Construct a DataFrame based on the weekly_index. This could be
# the visitor count of a museum that only opens on Sundays.
pd.DataFrame(data=[21, 15, 33, 34],
columns=["visitors"], index=weekly_index)
Out[4]: visitors
2020-01-05 21
2020-01-12 15
2020-01-19 33
2020-01-26 34
Com séries temporais, é sempre uma boa ideia certificar-se de que o índice está classificado
corretamente antes de iniciar sua análise:
In [12]: msft = msft.sort_index()
Filtrando um DatetimeIndex
Se seu DataFrame tiver um DatetimeIndex, há uma maneira fácil de selecionar linhas de
um período de tempo específico usando loc com uma string no formato YYYY-MM-DD
HH:MM:SS. pandas transformará essa string em uma fatia para cobrir todo o período. Por
exemplo, para selecionar todas as linhas de 2019, forneça o ano como uma string, não como
um número:
Página 131 Capítulo 6
In [14]: msft.loc["2019", "Adj Close"]
Out[14]: Date
2019-01-02 99.099190
2019-01-03 95.453529
2019-01-04 99.893005
2019-01-07 100.020401
2019-01-08 100.745613
...
2019-12-24 156.515396
2019-12-26 157.798309
2019-12-27 158.086731
2019-12-30 156.724243
2019-12-31 156.833633
Name: Adj Close, Length: 252, dtype: float64
Vamos dar um passo adiante e plotar os dados entre junho de 2019 e maio de 2020 (consulte
a Figura 6- 1):
In [15]: msft.loc["2019-06":"2020-05", "Adj Close"].plot()
Passe o mouse sobre o gráfico Plotly para ler o valor como uma dica de ferramenta e amplie
desenhando um retângulo com o mouse. Clique duas vezes no gráfico para voltar à
visualização padrão.
Usaremos o preço de fechamento ajustado na próxima seção para aprender sobre como lidar
com o fuso horário.
Se você deseja converter os timestamps para Fuso horário UTC, use o método DataFrame
tz_convert. UTC significa Coordinated Universal Time e é o sucessor do Greenwich
Mean Time (GMT). Observe como as horas de fechamento mudam em UTC dependendo
se o horário de verão (DST) está em vigor ou não em Nova York:
In [18]: msft_close = msft_close.tz_convert("UTC")
msft_close.loc["2020-01-02", "Adj Close"] # 21:00 without DST
Out[18]: Date
2020-01-02 21:00:00+00:00 159.737595
Name: Adj Close, dtype: float64
In [19]: msft_close.loc["2020-05-01", "Adj Close"] # 20:00 with DST
Out[19]: Date
2020-05-01 20:00:00+00:00 174.085175
Name: Adj Close, dtype: float64
A preparação de séries temporais como essa permitirá que você compare os preços de
fechamento das bolsas de valores em diferentes fusos horários, mesmo que as informações
de horário estejam ausentes ou indicadas no fuso horário local.
Agora que você sabe o que é um DatetimeIndex, vamos experimentar algumas
manipulações comuns de séries temporais na próxima seção, calculando e comparando o
desempenho das ações.
Página 133 Capítulo 6
Manipulações Comuns de Séries Temporais
Nesta seção, mostrarei como realizar tarefas comuns de análise de séries temporais, como
calcular retornos de ações, traçar o desempenho de várias ações e visualizar a correlação de
seus retornos em um mapa de calor. Também veremos como alterar a frequência das séries
temporais e como calcular estatísticas contínuas.
Com pandas, em vez de ter uma fórmula acessando duas linhas diferentes, você usa o
método shift para deslocar os valores em uma linha. Isso permite que você opere em uma
única linha para que seus cálculos possam usar a vetorização. shift aceita um inteiro
positivo ou negativo que desloca a série temporal para baixo ou para cima pelo respectivo
número de linhas. Vamos primeiro ver como funciona o shift:
In [20]: msft_close.head()
Out[20]: Adj Close
Date
1986-03-13 21:00:00+00:00 0.062205
1986-03-14 21:00:00+00:00 0.064427
1986-03-17 21:00:00+00:00 0.065537
Página 134 Capítulo 6
1986-03-18 21:00:00+00:00 0.063871
1986-03-19 21:00:00+00:00 0.062760
In [21]: msft_close.shift(1).head()
Out[21]: Adj Close
Date
1986-03-13 21:00:00+00:00 NaN
1986-03-14 21:00:00+00:00 0.062205
1986-03-17 21:00:00+00:00 0.064427
1986-03-18 21:00:00+00:00 0.065537
1986-03-19 21:00:00+00:00 0.063871
Agora você pode escrever uma única fórmula baseada em vetor que é fácil de ler e entender.
Para obter o logaritmo natural, use o ufunc do NumPy log, que é aplicado a cada elemento.
Então podemos traçar um histograma (veja a Figura 6-3):
In [22]: returns = np.log(msft_close / msft_close.shift(1))
returns = returns.rename(columns={"Adj Close": "returns"})
returns.head()
Out[22]: returns
Date
1986-03-13 21:00:00+00:00 NaN
1986-03-14 21:00:00+00:00 0.035097
1986-03-17 21:00:00+00:00 0.017082
1986-03-18 21:00:00+00:00 -0.025749
1986-03-19 21:00:00+00:00 -0.017547
In [23]: # Plot a histogram with the daily log returns
returns.plot.hist()
Para obter retornos simples, use o método embutido dos pandas pct_change. Por padrão,
ele calcula a variação percentual da linha anterior, que também é a definição de retornos
simples:
Página 135 Capítulo 6
In [24]: simple_rets = msft_close.pct_change()
simple_rets = simple_rets.rename(columns={"Adj Close": "simple rets"})
simple_rets.head()
Out[24]: simple rets
Date
1986-03-13 21:00:00+00:00 NaN
1986-03-14 21:00:00+00:00 0.035721
1986-03-17 21:00:00+00:00 0.017229
1986-03-18 21:00:00+00:00 -0.025421
1986-03-19 21:00:00+00:00 -0.017394
Até agora, analisamos apenas as ações da Microsoft. Na próxima seção, vamos carregar mais
séries temporais para que possamos dar uma olhada em outros métodos DataFrame que
requerem várias séries temporais.
Rebase e Correlação
As coisas ficam um pouco mais interessantes quando trabalhamos com mais de uma série
temporal. Vamos carregar alguns preços de fechamento ajustados adicionais para Amazon
(AMZN), Google (GOOGL) e Apple (AAPL), também baixados do Yahoo! Finanças:
In [25]: parts = [] # List to collect individual DataFrames
for ticker in ["AAPL", "AMZN", "GOOGL", "MSFT"]:
# "usecols" allows us to only read in the Date and Adj Close
adj_close = pd.read_csv(f"csv/{ticker}.csv",
index_col="Date", parse_dates=["Date"],
usecols=["Date", "Adj Close"])
# Rename the column into the ticker symbol
adj_close = adj_close.rename(columns={"Adj Close": ticker})
# Append the stock's DataFrame to the parts list
parts.append(adj_close)
In [26]: # Combine the 4 DataFrames into a single DataFrame
adj_close = pd.concat(parts, axis=1)
adj_close
Out[26]: AAPL AMZN GOOGL MSFT
Date
1980-12-12 0.405683 NaN NaN NaN
1980-12-15 0.384517 NaN NaN NaN
1980-12-16 0.356296 NaN NaN NaN
1980-12-17 0.365115 NaN NaN NaN
1980-12-18 0.375698 NaN NaN NaN
... ... ... ... ...
2020-05-22 318.890015 2436.879883 1413.239990 183.509995
2020-05-26 316.730011 2421.860107 1421.369995 181.570007
2020-05-27 318.109985 2410.389893 1420.280029 181.809998
2020-05-28 318.250000 2401.100098 1418.239990 NaN
2020-05-29 317.940002 2442.370117 1433.520020 NaN
Vamos agora rebasear os preços para que todas as séries temporais comecem em 100. Isso
nos permite comparar seu desempenho relativo em um gráfico; veja a Figura 6-4. Para
rebasear uma série temporal, divida cada valor por seu valor inicial e multiplique por 100, a
nova base. Se você fizesse isso no Excel, normalmente escreveria uma fórmula com uma
combinação de referências de células absolutas e relativas e, em seguida, copiaria a fórmula
para cada linha e cada série temporal. Nos pandas, graças à vetorização e transmissão, você
está lidando com uma única fórmula:
In [28]: # Use a sample from June 2019 - May 2020
adj_close_sample = adj_close.loc["2019-06":"2020-05", :]
rebased_prices = adj_close_sample / adj_close_sample.iloc[0, :] * 100
rebased_prices.head(2)
Out[28]: AAPL AMZN GOOGL MSFT
Date
2019-06-03 100.000000 100.000000 100.00000 100.000000
2019-06-04 103.658406 102.178197 101.51626 102.770372
In [29]: rebased_prices.plot()
Página 137 Capítulo 6
Para ver quão independentes são os retornos das diferentes ações, dê uma olhada em suas
correlações usando o método corr. Infelizmente, pandas não fornece um tipo de gráfico
embutido para visualizar a matriz de correlação como um mapa de calor, então precisamos
usar Plotly diretamente por meio de sua interface plotly.express (veja Figura 6-5):
In [30]: # Correlation of daily log returns
returns = np.log(adj_close / adj_close.shift(1))
returns.corr()
Out[30]: AAPL AMZN GOOGL MSFT
AAPL 1.000000 0.424910 0.503497 0.486065
AMZN 0.424910 1.000000 0.486690 0.485725
GOOGL 0.503497 0.486690 1.000000 0.525645
MSFT 0.486065 0.485725 0.525645 1.000000
Se você quiser entender como funciona o imshow em detalhes, dê uma olhada no Plotly
Documentos de API expressos.
Página 138 Capítulo 6
Neste ponto, já aprendemos algumas coisas sobre séries temporais, incluindo como
combiná-las e limpá-las e como calcular retornos e correlações. Mas e se você decidir que os
retornos diários não são uma boa base para sua análise e deseja retornos mensais? Como você
altera a frequência dos dados da série temporal é o tópico da próxima seção.
Reamostragem
Uma tarefa regular com séries temporais é aumentar e diminuir. Upsampling significa que
a série temporal é convertida em uma de maior frequência, e downsampling significa que
ela é convertida em uma de menor frequência. Em fichas financeiras, muitas vezes você
mostra o desempenho mensal ou trimestral, por exemplo. Para transformar a série temporal
diária em mensal, use o método resample que aceita uma string de frequência como M para
fim do mês-calendário ou BM para fim do mês comercial. Você pode encontrar uma lista
de todas as strings de frequência nos documentos do pandas. Semelhante a como funciona
groupby, você encadeia um método que define como você está reamostrando. Estou usando
last para sempre tirar a última observação daquele mês:
In [33]: end_of_month = adj_close.resample("M").last()
end_of_month.head()
Out[33]: AAPL AMZN GOOGL MSFT
Date
2004-08-31 2.132708 38.139999 51.236237 17.673630
2004-09-30 2.396127 40.860001 64.864868 17.900215
2004-10-31 3.240182 34.130001 95.415413 18.107374
2004-11-30 4.146072 39.680000 91.081078 19.344421
2004-12-31 3.982207 44.290001 96.491493 19.279480
Página 139 Capítulo 6
Em vez de last, você pode escolher qualquer outro método que funcione em groupby,
como sum ou mean. Há também ohlc, que retorna convenientemente os valores de
abertura, alta, baixa e fechamento durante esse período. Isso pode servir como fonte para
criar os gráficos de velas típicos que são frequentemente usados com preços de ações.
Se essa série temporal de fim de mês for tudo o que você tem e você precisa produzir uma
série temporal semanal a partir dela, você precisa aumentar a amostragem de sua série
temporal. Ao usar asfreq, você está dizendo aos pandas para não aplicar nenhuma
transformação e, portanto, verá a maioria dos valores mostrando NaN. Se você quiser
preencher o último valor conhecido, use o método ffill:
In [34]: end_of_month.resample("D").asfreq().head() # No transformation
Out[34]: AAPL AMZN GOOGL MSFT
Date
2004-08-31 2.132708 38.139999 51.236237 17.67363
2004-09-01 NaN NaN NaN NaN
2004-09-02 NaN NaN NaN NaN
2004-09-03 NaN NaN NaN NaN
2004-09-04 NaN NaN NaN NaN
In [35]: end_of_month.resample("W-FRI").ffill().head() # Forward fill
Out[35]: AAPL AMZN GOOGL MSFT
Date
2004-09-03 2.132708 38.139999 51.236237 17.673630
2004-09-10 2.132708 38.139999 51.236237 17.673630
2004-09-17 2.132708 38.139999 51.236237 17.673630
2004-09-24 2.132708 38.139999 51.236237 17.673630
2004-10-01 2.396127 40.860001 64.864868 17.900215
A redução de dados é uma maneira de suavizar uma série temporal. Calcular estatísticas em
uma janela contínua é outra maneira, como veremos a seguir.
Janelas rolantes
Quando você calcula estatísticas de séries temporais, geralmente deseja uma estatística
contínua, como a média móvel. A média móvel analisa um subconjunto da série temporal
(digamos 25 dias) e obtém a média desse subconjunto antes de avançar a janela em um dia.
Isso resultará em uma nova série temporal mais suave e menos propensa a valores
discrepantes. Se você gosta de negociação algorítmica, pode estar olhando para a interseção
da média móvel com o preço das ações e tomar isso (ou alguma variação dela) como um sinal
de negociação. Os DataFrames possuem um método rolling, que aceita o número de
observações como argumento. Você então o encadeia com o método estatístico que deseja
usar - no caso da média móvel, é a mean. Observando a Figura 6-6, você pode facilmente
comparar a série temporal original com a média móvel suavizada:
In [36]: # Plot the moving average for MSFT with data from 2019
msft19 = msft.loc["2019", ["Adj Close"]].copy()
Página 140 Capítulo 6
# Add the 25 day moving average as a new column to the DataFrame
msft19.loc[:, "25day average"] = msft19["Adj Close"].rolling(25).mean()
msft19.plot()
Em vez de mean, você pode usar muitas outras medidas estatísticas, incluindo count, sum,
median, min, max, std (desvio padrão) ou var (variância).
Neste ponto, vimos a funcionalidade mais importante dos pandas. É igualmente
importante, porém, entender onde os pandas têm seus limites, mesmo que eles ainda estejam
longe agora.
CAPÍTULO 7
Manipulação de arquivos do Excel
com pandas
Após seis capítulos de intensas introduções a ferramentas, Python e pandas, farei uma pausa
e iniciarei este capítulo com um estudo de caso prático que permite que você use bem suas
habilidades recém-adquiridas: com apenas dez linhas de código pandas, você consolidará
dezenas de arquivos do Excel em um relatório do Excel, pronto para ser enviado aos seus
gerentes. Após o estudo de caso, darei uma introdução mais detalhada às ferramentas que o
pandas oferece para trabalhar com arquivos do Excel: a função read_excel e a classe
ExcelFile para leitura e o método to_excel e a classe ExcelWriter para escrever
arquivos do Excel . O pandas não depende do aplicativo Excel para ler e gravar arquivos
Excel, o que significa que todos os exemplos de código neste capítulo são executados em
todos os lugares em que o Python é executado, incluindo o Linux.
Os arquivos do Excel existentes na subpasta são praticamente os mesmos, exceto que estão
faltando a coluna status e são armazenados no formato legado xls. Como primeiro passo,
vamos ler as novas transações de janeiro com a função dos pandas read_excel:
In [1]: import pandas as pd
In [2]: df = pd.read_excel("sales_data/new/January.xlsx")
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9493 entries, 0 to 9492
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 transaction_id 9493 non-null object
1 store 9493 non-null object
2 status 9493 non-null object
3 transaction_date 9493 non-null datetime64[ns]
4 plan 9493 non-null object
5 contract_type 9493 non-null object
6 amount 9493 non-null float64
dtypes: datetime64[ns](1), float64(1), object(5)
memory usage: 519.3+ KB
Como você pode ver, o pandas reconheceu corretamente os tipos de dados de todas as
colunas, incluindo o formato de data de transaction_date. Isso nos permite trabalhar
com os dados sem preparação adicional. Como este exemplo é deliberadamente simples,
podemos continuar criando um script curto chamado sales_report_pandas.py conforme
mostrado no Exemplo 7-1. Esse script lerá todos os arquivos do Excel de ambos os diretórios,
agregará os dados e gravará a tabela de resumo em um novo arquivo do Excel.
Página 144 Capítulo 7
Use o VS Code para escrever o script você mesmo ou abra-o no repositório complementar.
Para uma atualização sobre como criar ou abrir arquivos no VS Code, dê outra olhada no
Capítulo 2. Se você mesmo o criar, certifique-se de colocá-lo ao lado da pasta sales_data—
isso permitirá que você execute o script sem precisar ajustar nenhum caminho de arquivo.
import pandas as pd
# Pivot each store into a column and sum up all transactions per date
pivot = pd.pivot_table(df,
index="transaction_date", columns="store",
values="amount", aggfunc="sum")
Até este capítulo, eu estava usando strings para especificar caminhos de arquivos.
Usando a classe Path do módulo da biblioteca padrão pathlib em vez disso, você
obtém acesso a um poderoso conjunto de ferramentas: objetos de caminho permitem
que você construa facilmente caminhos concatenando partes individuais por meio de
barras, como é feito quatro linhas abaixo com this_dir / "sales_data". Esses
caminhos funcionam entre plataformas e permitem que você aplique filtros como
rglob conforme explicado no próximo ponto. file resolve para o caminho do
arquivo de código-fonte quando você o executa — usando seu parent lhe dará,
portanto, o nome do diretório deste arquivo. O método resolve que usamos antes de
chamar parent transforma o caminho em um caminho absoluto.
Página 145 Capítulo 7
Se você executar isso de um notebook Jupyter, você teria que substituir esta linha por
this_dir = Path(".").resolve(), com o ponto representando o diretório atual.
Na maioria dos casos, funções e classes que aceitam um caminho na forma de uma
string também aceitam um objeto de caminho.
Tudo parece bom, exceto a coluna Flagship— seu tipo de dados deve ser bool em vez de
object. Para corrigir isso, podemos fornecer uma função conversora que lida com as células
ofensivas nessa coluna (em vez de escrever a função fix_missing, poderíamos também
fornece uma expressão lambda):
In [5]: def fix_missing(x):
return False if x in ["", "MISSING"] else x
In [6]: df = pd.read_excel("xl/stores.xlsx",
sheet_name="2019", skiprows=1, usecols="B:F",
converters={"Flagship": fix_missing})
df
Out[6]: Store Employees Manager Since Flagship
0 New York 10 Sarah 2018-07-20 False
1 San Francisco 12 Neriah 2019-11-02 False
2 Chicago 4 Katelin 2020-01-31 False
3 Boston 5 Georgiana 2017-04-01 True
4 Washington DC 3 Evan NaT False
5 Las Vegas 11 Paul 2020-01-06 False
In [7]: # The Flagship column now has Dtype "bool"
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Store 6 non-null object
1 Employees 6 non-null int64
2 Manager 6 non-null object
Página 148 Capítulo 7
3 Since 5 non-null datetime64[ns]
4 Flagship 6 non-null bool
dtypes: bool(1), datetime64[ns](1), int64(1), object(2)
memory usage: 326.0+ bytes
A função read_excel também aceita uma lista de nomes de planilhas. Neste caso, retorna
um dicionário com o DataFrame como valor e o nome da planilha como chave. Para ler
todas as planilhas, você precisa fornecer sheet_name=None. Além disso, observe a pequena
variação de como estou usando usecols fornecendo os nomes das colunas da tabela:
In [8]: sheets = pd.read_excel("xl/stores.xlsx", sheet_name=["2019", "2020"],
skiprows=1, usecols=["Store", "Employees"])
sheets["2019"].head(2)
Out[8]: Store Employees
0 New York 10
1 San Francisco 12
Se o arquivo de origem não tiver cabeçalhos de coluna, defina header=None e forneça-os via
names. Observe que sheet_name também aceita índices de planilha:
In [9]: df = pd.read_excel("xl/stores.xlsx", sheet_name=0,
skiprows=2, skipfooter=3,
usecols="B:C,F", header=None,
names=["Branch", "Employee_Count", "Is_Flagship"])
df
Out[9]: Branch Employee_Count Is_Flagship
0 New York 10 False
1 San Francisco 12 MISSING
2 Chicago 4 NaN
pandas oferece uma maneira alternativa de ler arquivos do Excel usando a classe ExcelFile.
Isso faz a diferença principalmente se você quiser ler em várias planilhas de um arquivo no
formato legado xls: nesse caso, usar ExcelFile será mais rápido, pois evita que os pandas
leiam o arquivo inteiro várias vezes. ExcelFile pode ser usado como um gerenciador de
contexto (veja a barra lateral) para que o arquivo seja fechado corretamente novamente.
Página 149 Capítulo 7
Gerenciadores de contexto e a instrução with
Em primeiro lugar, a with em Python não tem nada a ver com a instrução With em VBA:
em VBA, ela é usada para executar uma série de instruções no mesmo objeto, enquanto em
Python, ele é usado para gerenciar recursos como arquivos ou conexões de banco de dados.
Se você quiser carregar os dados de vendas mais recentes para poder analisá-los, talvez seja
necessário abrir um arquivo ou estabelecer uma conexão com um banco de dados. Depois
de terminar de ler os dados, é uma prática recomendada fechar o arquivo ou a conexão o
mais rápido possível novamente. Caso contrário, você pode se deparar com situações em
que não pode abrir outro arquivo ou estabelecer outra conexão com o banco de dados —
manipuladores de arquivos e conexões de banco de dados são recursos limitados. Abrir e
fechar um arquivo de texto manualmente funciona assim (w significa abrir o arquivo no
modo write, que substitui o arquivo se ele já existir):
In [11]: f = open("output.txt", "w")
f.write("Some text")
f.close()
execução deste código criará um arquivo chamado output.txt no mesmo diretório do
notebook em que você o está executando e escreverá “algum texto” nele. Para ler um
arquivo, você usaria r em vez de w e para anexar ao final do arquivo, usaria a. Como os
arquivos também podem ser manipulados de fora do seu programa, tal operação pode
falhar. Você pode lidar com isso usando o mecanismo try/except que apresentarei no
Capítulo 11. No entanto, como essa é uma operação tão comum, o Python está fornecendo
a instrução with para facilitar as coisas:
In [12]: with open("output.txt", "w") as f:
f.write("Some text")
df1
Out[13]: Store Employees Manager Since Flagship
0 New York 10 Sarah 2018-07-20 False
1 San Francisco 12 Neriah 2019-11-02 MISSING
Página 150 Capítulo 7
ExcelFile também dá acesso aos nomes de todas as planilhas:
In [14]: stores = pd.ExcelFile("xl/stores.xlsx")
stores.sheet_names
Out[14]: ['2019', '2020', '2019-2020']
Finalmente, pandas permite que você leia arquivos do Excel a partir de um URL, semelhante
ao que fizemos com arquivos CSV no Capítulo 5. Vamos lê-lo diretamente do repositório
complementar:
In [15]: url = ("https://raw.githubusercontent.com/fzumstein/"
"python-for-excel/1st-edition/xl/stores.xlsx")
pd.read_excel(url, skiprows=1, usecols="B:E", nrows=2)
Out[15]: Store Employees Manager Since
0 New York 10 Sarah 2018-07-
20
1 San Francisco 12 Neriah 2019-11-02
Para resumir, a Tabela 7-1 mostra os parâmetros mais usados read_excel. Você
encontrará a lista completa nos documentos oficiais.
Tanto para ler arquivos do Excel com pandas - vamos agora mudar de lado e aprender sobre
como escrever Arquivos do Excel na próxima seção!
Conclusão
O bom do pandas é que ele oferece uma interface consistente para trabalhar com todos os
formatos de arquivo do Excel suportados, seja xls, xlsx, xlsmou xlsb. Isso facilitou a leitura
de um diretório de arquivos do Excel, agregar os dados e despejar o resumo em um relatório
do Excel — em apenas dez linhas de código.
pandas, no entanto, não faz o trabalho pesado em si: sob o capô, ele seleciona um pacote de
leitor ou gravador para fazer o trabalho. No próximo capítulo, mostrarei quais pacotes de
leitor e escritor o pandas usa e como você os usa diretamente ou em combinação com o
pandas. Isso nos permitirá contornar as limitações que vimos na seção anterior.
Página 154
CAPÍTULO 8
Manipulação de arquivos do Excel
com Pacotes Reader e Writer
Este capítulo apresenta OpenPyXL, XlsxWriter, pyxlsb, xlrd e xlwt: estes são os pacotes que
podem ler e escrever arquivos Excel e são usados pelos pandas quando você chama as
funções read_excel ou to_excel. O uso direto dos pacotes de leitor e gravador permite
criar relatórios mais complexos do Excel, bem como ajustar o processo de leitura. Além
disso, se você trabalhar em um projeto em que só precisa ler e gravar arquivos do Excel sem
a necessidade do restante da funcionalidade do pandas, instalar a pilha completa do
NumPy/pandas provavelmente seria um exagero. Começaremos este capítulo aprendendo
quando usar qual pacote e como sua sintaxe funciona antes de examinar alguns tópicos
avançados, incluindo como trabalhar com arquivos grandes do Excel e como combinar
pandas com os pacotes de leitor e gravador para melhorar o estilo de DataFrames. Para
concluir, retomamos o estudo de caso do início do último capítulo e aprimorarmos o
relatório do Excel formatando a tabela e adicionando um gráfico. Como o último capítulo,
este capítulo não requer uma instalação do Excel, o que significa que todos os exemplos de
código são executados no Windows, macOS e Linux.
• OpenPyXL
• XlsxWriter
• pyxlsb
• xlrd
• xlwt
• xlutils
Para entender qual pacote pode fazer o quê, dê uma olhada na Tabela 8-1. Por exemplo,
para ler o formato de arquivo xlsx, você terá que usar o pacote OpenPyXL:
Se você deseja gravar arquivos xlsx ou xlsm, você precisa decidir entre OpenPyXL e
XlsxWriter. Ambos os pacotes cobrem funcionalidades semelhantes, mas cada pacote pode
ter alguns recursos exclusivos que o outro não possui. Como ambas as bibliotecas estão
sendo desenvolvidas ativamente, isso está mudando ao longo do tempo. Aqui está uma visão
geral de alto nível de onde eles diferem:
pandas usa o pacote writer que pode encontrar e se você tiver o OpenPyXL e o XlsxWriter
instalados, o XlsxWriter é o padrão. Se você quiser escolher qual pacote os pandas devem
usar, especifique o parâmetro engine nas funções read_excel ou to_excel ou as classes
ExcelFile e ExcelWriter, respectivamente. O mecanismo é o nome do pacote em letras
minúsculas, portanto, para escrever um arquivo com OpenPyXL em vez de XlsxWriter,
execute o seguinte:
df.to_excel("filename.xlsx", engine="openpyxl")
Uma vez que você sabe qual pacote você precisa, há um segundo desafio esperando por você:
a maioria desses pacotes requer que você escreva um pouco de código para ler ou escrever
um intervalo de células, e cada pacote usa uma sintaxe diferente. Para facilitar sua vida, criei
um módulo auxiliar que apresentarei a seguir.
O módulo excel.py
Criei o excel.py para facilitar sua vida ao usar os pacotes de leitor e gravador, pois ele cuida
dos seguintes problemas:
Package switching
Ter que trocar o pacote de leitor ou gravador é um cenário relativamente comum. Por
exemplo, os arquivos do Excel tendem a aumentar de tamanho ao longo do tempo, o
que muitos usuários combatem mudando o formato do arquivo de xlsx para xlsb, pois
isso pode reduzir substancialmente o tamanho do arquivo. Nesse caso, você terá que
mudar de OpenPyXL para pyxlsb. Isso força você a reescrever seu código OpenPyXL
para refletir a sintaxe do pyxlsb.
Conversão de tipo de dados
Isso está ligado ao ponto anterior: ao trocar de pacote, você não precisa apenas ajustar
a sintaxe do seu código, mas também precisa ficar atento aos diferentes tipos de dados
que esses pacotes retornam para o mesmo conteúdo da célula. Por exemplo,
OpenPyXL retorna None para células vazias, enquanto xlrd retorna uma string vazia.
Página 157 Capítulo 8
Cell looping
Os pacotes do leitor e do gravador são pacotes baixo nível: isso significa que eles não
possuem funções convenientes que permitiriam que você resolva tarefas comuns
facilmente. Por exemplo, a maioria dos pacotes exige que você percorra cada célula que
você vai ler ou escrever.
Você encontrará o módulo excel.py no repositório complementar e o usaremos nas
próximas seções, mas como uma prévia, aqui está a sintaxe para ler e escrever valores:
import excel
values = excel.read(sheet_object, first_cell="A1", last_cell=None)
excel.write(sheet_object, values, first_cell="A1")
A função read aceita um xlrd , OpenPyXL ou pyxlsb. Ele também aceita os argumentos
opcionais first_cell e last_cell. Eles podem ser fornecidos na A1 ou como tupla linha-
coluna com os índices baseados em um do Excel: (1, 1). O valor padrão para first_cell
é A1 enquanto o valor padrão para last_cell é o canto inferior direito do intervalo usado.
Portanto, se você fornecer apenas o objeto sheet, ele vai ler a folha inteira. A função de
escrita funciona de forma semelhante: espera um objeto sheet de xlwt, OpenPyXL ou
XlsxWriter junto com os valores como lista aninhada e um first_cell, que marca o canto
superior esquerdo de onde a lista aninhada será gravada. O módulo excel.py também
harmoniza a conversão do tipo de dados conforme mostrado na Tabela 8-2.
Equipado com o módulo excel.py, agora estamos prontos para mergulhar nos pacotes: as
próximas quatro seções são sobre OpenPyXL, XlsxWriter, pyxlsb e xlrd/xlwt/xlutils. Eles
seguem um estilo de livro de receitas que permite que você comece rapidamente com cada
pacote. Em vez de lê-lo sequencialmente, recomendo que você escolha o pacote de que
precisa com base na Tabela 8-1 e, em seguida, pule diretamente para a seção correspondente.
Página 158 Capítulo 8
A instrução with
Usaremos a instrução with em várias ocasiões neste capítulo. Se você precisar de
uma atualização, dê uma olhada na barra lateral “Gerenciadores de contexto e a
instrução with” na página 150 no Capítulo 7.
OpenPyXL
OpenPyXL é o único pacote nesta seção que pode ler e gravar arquivos do Excel. Você pode
até usá-lo para editar arquivos do Excel - embora apenas os simples. Vamos começar
analisando como a leitura funciona!
# Image
sheet.add_image(Image("images/python.png"), "C1")
# Chart
chart = BarChart()
chart.type = "col"
chart.title = "Sales Per Region"
chart.x_axis.title = "Regions"
chart.y_axis.title = "Sales"
chart_data = Reference(sheet, min_row=11, min_col=1,
max_row=12, max_col=3)
chart_categories = Reference(sheet, min_row=10, min_col=2,
max_row=10, max_col=3)
# from_rows interprets the data in the same way
# as if you would add a chart manually in Excel
chart.add_data(chart_data, titles_from_data=True, from_rows=True)
chart.set_categories(chart_categories)
sheet.add_chart(chart, "A15")
Se você quiser escrever um arquivo de modelo do Excel, precisará definir o atributo template
para True antes de salvá-lo:
In [11]: book = openpyxl.Workbook()
sheet = book.active
sheet["A1"].value = "This is a template"
book.template = True
book.save("template.xltx")
Como você pode ver no código, o OpenPyXL está definindo cores fornecendo uma string
como FF0000. Este valor é composto por três valores hexadecimais ( FF, 00 e 00) que
correspondem aos valores vermelho/verde/azul da cor desejada. Hex significa hexadecimal
e representa números usando uma base de dezesseis em vez de uma base de dez que é usada
pelo nosso sistema decimal padrão.
Página 161 Capítulo 8
Encontrando o valor hexadecimal de uma cor
Para encontrar o valor hexadecimal desejado de uma cor no Excel, clique na lista
suspensa de tinta que você usaria para alterar a cor de preenchimento de uma célula
e selecione Mais cores. Agora selecione sua cor e leia seu valor hexadecimal no
menu.
XlsxWriter
Como o nome sugere, XlsxWriter só pode gravar arquivos do Excel. O código a seguir
produz a mesma pasta de trabalho que produzimos anteriormente com o OpenPyXL, que
é mostrada na Figura 8-1. Observe que o XlsxWriter usa índices de célula baseados em zero,
enquanto o OpenPyXL usa índices de célula baseados em um — certifique-se de levar isso
em consideração se você alternar entre os pacotes:
In [14]: import datetime as dt
import xlsxwriter
import excel
In [15]: # Instantiate a workbook
book = xlsxwriter.Workbook("xlxswriter.xlsx")
# Image
sheet.insert_image(0, 2, "images/python.png")
Em comparação com o OpenPyXL, o XlsxWriter tem que adotar uma abordagem mais
complicada para gravar arquivos xlsm , pois é um pacote de gravação puro. Primeiro, você
precisa extrair o código da macro de um arquivo Excel existente no Anaconda Prompt (o
exemplo usa o arquivo macro.xlsm, que você encontrará na xl pasta
Windows
Comece alterando para o diretório xl, encontre o caminho para vba_extract.py, um
script que vem com XlsxWriter:
Página 164 Capítulo 8
(base)> cd C:\Users\username\python-for-excel\xl
(base)> onde vba_extract.py
C:\Users\username\Anaconda3\Scripts\vba_extract.py
Em seguida, use este caminho no seguinte comando:
(base)> python C:\...\Anaconda3\Scripts\vba_extract.py macro.xlsm
macOS
No macOS, o comando está disponível como executável script e pode ser executado
assim:
(base)> cd /Users/username/python-for-excel/xl
(base)> vba_extract.py macro.xlsm
Isso salvará o arquivo vbaProject.bin no diretório onde você está executando o comando.
Também incluí o arquivo extraído na pasta xl do repositório complementar. Vamos usá-lo
no exemplo a seguir para escrever uma pasta de trabalho com um botão de macro:
In [16]: book = xlsxwriter.Workbook("macro_xlxswriter.xlsm")
sheet = book.add_worksheet("Sheet1")
sheet.write("A1", "Click the button!")
book.add_vba_project("xl/vbaProject.bin")
sheet.insert_button("A3", {"macro": "Hello", "caption": "Button 1",
"width": 130, "height": 35})
book.close()
pyxlsb
Comparado com outras bibliotecas de leitores, pyxlsb oferece menos funcionalidade, mas é
sua única opção quando se trata de ler arquivos do Excel no formato binário xlsb. pyxlsb
não faz parte do Anaconda, então você precisará instalá-lo se ainda não tiver feito isso.
Atualmente, também não está disponível via Conda, então use pip para instalá-lo:
(base)> pip install pyxlsb
Lembre-se, quando você lê o formato de arquivo xlsb com uma versão de pandas abaixo
de 1.3, você precisa para especificar o mecanismo explicitamente:
In [21]: df = pd.read_excel("xl/stores.xlsb", engine="pyxlsb")
Intervalo usado
Ao contrário de OpenPyXL e pyxlsb, xlrd retorna as dimensões das células com
um valor, em vez do intervalo usado de uma planilha ao usar sheet.nrows e
sheet.ncols. O que o Excel retorna como intervalo usado geralmente contém
linhas e colunas vazias na parte inferior e na borda direita do intervalo. Isso pode
acontecer, por exemplo, quando você exclui o conteúdo das linhas (pressionando
a tecla Delete), em vez de excluir as próprias linhas (clicando com o botão direito
do mouse e selecionando Excluir).
Página 167 Capítulo 8
Escrevendo com xlwt
O código a seguir reproduz o que fizemos anteriormente com OpenPyXL e XlsxWriter,
conforme mostrado na Figura 8-1. xlwt, no entanto, não pode produzir gráficos e suporta
apenas o formato bmp para imagens:
In [30]: import xlwt
from xlwt.Utils import cell_to_rowcol2
import datetime as dt
import excel
In [31]: # Instantiate a workbook
book = xlwt.Workbook()
Os tempos de CPU medem o tempo gasto em CPU, que pode ser menor que o wall time (se
o programa tiver que esperar que a CPU fique disponível) ou maior (se o programa estiver
sendo executado em vários núcleos de CPU em paralelo). Para medir o tempo com mais
precisão, use %%timeit em vez de %%time, que executa a célula várias vezes e obtém a média
de todas as execuções. %%time e %%timeit são células mágicas, ou seja, eles precisam estar
na primeira linha da célula e vão medir o tempo de execução de toda a célula. Se, em vez
disso, você quiser medir apenas uma única linha, inicie essa linha com %time ou %timeit.
Vamos ver o quanto mais rápido a versão paralelizada lê o arquivo big.xlsx que você
encontrará na pasta do repositório complementar xl:
In [39]: %%time
data = pd.read_excel("xl/big.xlsx",
sheet_name=None, engine="openpyxl")
Wall time: 49.5 s
In [40]: %%time
import parallel_pandas
data = parallel_pandas.read_excel("xl/big.xlsx", sheet_name=None)
Wall time: 12.1 s
Para obter o DataFrame que representa Sheet1, você escreveria data["Sheet1"] em ambos
os casos. Observando o tempo de parede de ambas as amostras, você verá que a versão
paralelizada foi várias vezes mais rápida que pd.read_excel com esta pasta de trabalho
específica e no meu laptop com 6 núcleos de CPU. Se você quiser ainda mais rápido, paralelize
o OpenPyXL diretamente: você também encontrará uma implementação para isso no
repositório complementar (parallel_openpyxl.py), junto com uma implementação para xlrd
para ler o formato legado xls em paralelo (parallel_xlrd.py).
Página 172 Capítulo 8
Passar pelos pacotes subjacentes em vez de pandas permitirá que você pule a transformação
em um DataFrame ou aplique apenas as etapas de limpeza necessárias, o que provavelmente
o ajudará a tornar as coisas mais rápidas - se essa for sua maior preocupação.
Na minha máquina e usando o big.xlsx , a execução do código a seguir levou cerca de cinco
segundos, enquanto pandas levou cerca de doze segundos:
import modin.pandas
data = modin.pandas.read_excel("xl/big.xlsx",
sheet_name=0, engine="openpyxl")
Agora que você sabe como lidar com arquivos grandes, vamos seguir em frente e ver como
podemos usar pandas e os pacotes de baixo nível juntos para melhorar a formatação padrão
ao gravar DataFrames em arquivos do Excel!
Esses exemplos usam OpenPyXL, mas funciona conceitualmente da mesma forma com os
outros pacotes. Vamos agora continuar descobrindo como podemos formatar o índice e o
cabeçalho de um DataFrame.
O resultado desse código é o mesmo mostrado na Figura 8-3. Para obter mais detalhes sobre
a abordagem de estilo DataFrame, consulte diretamente os documentos de estilo.
Sem ter que depender do atributo style, o pandas oferece suporte para formatar os objetos
date e datetime conforme mostrado na Figura 8-4:
In [51]: df = pd.DataFrame({"Date": [dt.date(2020, 1, 1)],
"Datetime": [dt.datetime(2020, 1, 1, 10)]})
with pd.ExcelWriter("date.xlsx",
date_format="yyyy-mm-dd",
datetime_format="yyyy-mm-dd hh:mm:ss") as writer:
df.to_excel(writer)
Agora que você sabe como formatar DataFrames no Excel, é hora de fazer outra tentativa
no estudo de caso do capítulo anterior e ver se podemos melhorar o relatório do Excel com
o conhecimento deste capítulo!
Conclusão
Neste capítulo, apresentei a você os pacotes de leitura e gravação que os pandas usam nos
bastidores. Usá-los diretamente nos permite ler e escrever pastas de trabalho do Excel sem
precisar ter pandas instalados. No entanto, usá-los em combinação com pandas nos permite
aprimorar os relatórios Excel DataFrame adicionando títulos, gráficos e formatação.
Embora os pacotes atuais de leitor e escritor sejam incrivelmente poderosos, ainda espero
que um dia vejamos um “momento NumPy” que una os esforços de todos os
desenvolvedores em um único projeto. Seria ótimo saber qual pacote usar sem ter que olhar
uma tabela primeiro e sem ter que usar uma sintaxe diferente para cada tipo de arquivo do
Excel. Nesse sentido, faz sentido começar com os pandas e voltar apenas para os pacotes de
leitor e gravador quando você precisar de funcionalidades adicionais que os pandas não
cobrem.
Página 179 Capítulo 8
O Excel, no entanto, é muito mais do que apenas um arquivo de dados ou um relatório: o
aplicativo Excel é uma das interfaces de usuário mais intuitivas, onde os usuários podem
digitar alguns números e obtê-los para exibir as informações que estão procurando.
Automatizar o aplicativo Excel em vez de ler e gravar arquivos Excel abre toda uma nova
gama de funcionalidades que vamos explorar na Parte IV. O próximo capítulo inicia essa
jornada mostrando como controlar remotamente o Excel a partir do Python.
Página 180
CAPÍTULO 9
Automação do Excel
Até agora, aprendemos como substituir tarefas típicas do Excel por pandas (Parte II) e como
usar arquivos do Excel como fonte de dados e formato de arquivo para seus relatórios (Parte
III). Este capítulo inicia a Parte IV, onde deixamos de manipular arquivos com os pacotes
leitor e gravador e começamos a automatizar o aplicativo com xlwings.
O principal caso de uso do xlwings é construir aplicativos interativos onde planilhas do
Excel atuam como a interface do usuário, permitindo que você chame Python clicando em
um botão ou chamando uma função definida pelo usuário — esse é o tipo de funcionalidade
que não é coberta por os pacotes de leitor e escritor. Mas isso não significa que o xlwings
não possa ser usado para ler e gravar arquivos, desde que você esteja no macOS ou no
Windows e tenha o Excel instalado. Uma vantagem que o xlwings tem nessa área é a
capacidade de editar verdadeiramente arquivos do Excel, em todos os formatos, sem alterar
ou perder nenhum conteúdo ou formatação existente. Outra vantagem é que você pode ler
os valores das células de uma pasta de trabalho do Excel sem a necessidade de salvá-la
primeiro. No entanto, também pode fazer todo o sentido usar um pacote de leitor/gravador
do Excel e xlwings juntos, como veremos quando retomarmos o estudo de caso de relatório
do Capítulo 7 mais uma vez.
Vou começar este capítulo apresentando o modelo de objeto do Excel, bem como o xlwings:
primeiro aprenderemos o básico, como conectar-se a uma pasta de trabalho ou ler e escrever
valores de células antes de aprofundar um pouco mais para entender como os conversores e
opções nos permitem para trabalhar com pandas DataFrames e matrizes NumPy. Também
analisamos como interagir com gráficos, imagens e nomes definidos antes de passar para a
última seção, que explica como funciona o xlwings sob o capô: isso fornecerá o
conhecimento necessário para melhorar o desempenho de seus scripts, bem como contornar
a falta funcionalidade.
Página 181 Capítulo 9
A partir deste capítulo, você precisará executar os exemplos de código no Windows ou no
macOS, pois eles dependem de uma instalação local do Microsoft Excel.1
Introdução ao xlwings
Um objetivo do xlwings é servir como um substituto imediato para o VBA, permitindo que
você interaja com o Excel a partir do Python no Windows e no macOS. Como a grade do
Excel é o layout perfeito para exibir as estruturas de dados do Python, como listas aninhadas,
matrizes NumPy e pandas DataFrames, um dos principais recursos do xlwings é facilitar ao
máximo a leitura e gravação de e para o Excel. Vou começar esta seção apresentando o Excel
como um visualizador de dados — isso é útil quando você está interagindo com DataFrames
em um notebook Jupyter. Em seguida, explicarei o modelo de objeto do Excel antes de
explorá-lo interativamente com xlwings. Para encerrar esta seção, mostrarei como chamar o
código VBA que você ainda pode ter em pastas de trabalho herdadas. Como o xlwings faz
parte do Anaconda, não precisamos instalá-lo manualmente.
1 No Windows, você precisa de pelo menos Excel 2007, e no macOS, você precisa no pelo menos Excel 2016. Como
alternativa, você pode instalar a versão desktop do Excel, que faz parte de sua assinatura do Microsoft 365.
Verifique sua assinatura para obter detalhes sobre como fazer isso.
Página 182 Capítulo 9
In [2]: # Vamos criar um DataFrame baseado em números pseudoaleatórios e
# com linhas suficientes para que apenas a cabeça e a cauda sejam
mostradas
df = pd.DataFrame(data=np.random.randn(100, 5),
columns=[f"Trial {i}" for i in range(1, 6)])
df
Out[2]: Trial 1 Trial 2 Trial 3 Trial 4 Trial 5
0 -1.313877 1.164258 -1.306419 -0.529533 -0.524978
1 -0.854415 0.022859 -0.246443 -0.229146 -0.005493
2 -0.327510 -0.492201 -1.353566 -1.229236 0.024385
3 -0.728083 -0.080525 0.628288 -0.382586 -0.590157
4 -1.227684 0.498541 -0.266466 0.297261 -1.297985
.. ... ... ... ... ...
95 -0.903446 1.103650 0.033915 0.336871 0.345999
96 -1.354898 -1.290954 -0.738396 -1.102659 0.115076
97 -0.070092 -0.416991 -0.203445 -0.686915 -1.163205
98 -1.201963 0.471854 -0.458501 -0.357171 1.954585
99 1.863610 0.214047 -1.426806 0.751906 -2.338352
A função view aceita todos os objetos Python comuns, incluindo números, strings, listas,
dicionários, tuplas, arrays NumPy e DataFrames pandas. Por padrão, ele abre uma nova
pasta de trabalho e cola o objeto na célula A1 da primeira planilha — até ajusta as larguras
das colunas usando a funcionalidade AutoAjuste do Excel. Em vez de abrir uma nova pasta
de trabalho toda vez, você também pode reutilizar a mesma fornecendo à função view
objeto xlwings sheet como segundo argumento: xw.view(df, mysheet). Como você
obtém acesso a tal objeto sheet e como ela se encaixa no modelo de objetos do Excel é o
que explicarei a seguir.2
2 Observe que o xlwings 0.22.0 introduziu a xw.load , que é semelhante ao xw.view, mas funciona na direção
oposta: permite carregar um intervalo do Excel facilmente em um notebook Jupyter como um DataFrame pandas,
consulte a documentação.
Página 183 Capítulo 9
macOS: Permissões e preferências
No macOS, certifique-se de executar os notebooks Jupyter e o VS Code a partir de
um prompt do Anaconda (ou seja, via Terminal), conforme mostrado no Capítulo
2. Isso garante que você será saudado por dois pop-ups ao usar o xlwings pela
primeira vez: o primeiro é “Terminal quer acesso para controlar eventos do
sistema” e o segundo é “Terminal quer acesso para controlar o Microsoft Excel”.
Você precisará confirmar ambos os pop-ups para permitir que o Python
automatize o Excel. Em teoria, esses pop-ups devem ser acionados por qualquer
aplicativo a partir do qual você execute o código xlwings, mas, na prática, isso
geralmente não é o caso, portanto, executá-los pelo Terminal evitará problemas.
Além disso, você precisará abrir as Preferências do Excel e desmarcar “Mostrar
Galeria de Pastas de Trabalho ao abrir o Excel” na categoria Geral. Isso abre o Excel
diretamente em uma pasta de trabalho vazia, em vez de abrir a galeria primeiro, o
que atrapalha quando você abrir uma nova instância do Excel por meio de xlwings.
As caixas tracejadas são coleções e contêm um ou mais objetos do mesmo tipo. Um app
corresponde a uma instância do Excel, ou seja, um aplicativo do Excel que é executado como
um processo separado. Os usuários avançados às vezes usam várias instâncias do Excel em
paralelo para abrir a mesma pasta de trabalho duas vezes, por exemplo, para calcular uma
pasta de trabalho com diferentes entradas em paralelo. Com as versões mais recentes do
Excel, a Microsoft tornou um pouco mais complicado abrir várias instâncias do Excel
manualmente: inicie o Excel e clique com o botão direito do mouse em seu ícone na barra
de tarefas do Windows. No menu que aparece, clique com o botão esquerdo do mouse na
entrada do Excel enquanto mantém pressionada a tecla Alt ao mesmo tempo (certifique-se
de manter a tecla Alt
Página 184 Capítulo 9
pressionada até soltar o botão do mouse) - um pop-up perguntará se você deseja iniciar uma
nova instância do Excel. No macOS, não há uma maneira manual de iniciar mais de uma
instância do mesmo programa, mas você pode iniciar várias instâncias do Excel
programaticamente via xlwings, como veremos mais adiante. Para resumir, uma instância
do Excel é um ambiente sandboxed, o que significa que uma instância não pode se
comunicar com a outra.3 O objetos sheet dá acesso a coleções como gráficos, imagens e
nomes definidos — tópicos que examinaremos na segunda seção deste capítulo.
Figura 9-1. O modelo de objeto do Excel conforme implementado pelo xlwings (excerto)
3 Consulte “O que são instâncias do Excel e por que isso é importante?” para obter mais informações sobre instâncias
separadas do Excel.
Página 185 Capítulo 9
Tenha em mente que isso só funciona se o seu “Símbolo decimal” (no mesmo menu)
também não for uma vírgula. Para substituir os separadores decimal e de milhares de todo o
sistema (mas não o separador de lista), no Excel, vá para “Opções” > “Avançado”, onde você
encontrará as configurações em “Opções de edição”.
No macOS, funciona de forma semelhante, exceto que você não pode alterar o separador de
lista diretamente: em Preferências do Sistema do seu macOS (não Excel), selecione Idioma
e Região. Lá, defina uma região específica globalmente (na guia Geral) ou especificamente
para o Excel (na guia Aplicativos).
Para ter uma ideia do modelo de objeto do Excel, como de costume, é melhor brincar com
ele interativamente. Vamos começar com a classe Book: ela permite criar novas pastas de
trabalho e conectar-se às existentes; consulte Tabela 9-1 para obter uma visão geral.
Tabela 9-1. Trabalhando com pastas de trabalho do Excel
Comando Descrição
xw.Book() Retorna um livro que representa uma nova pasta de
trabalho do Excel na instância ativa do Excel. Se não
houver instância ativa, o Excel será iniciado.
xw.Book("Book1") Retorna um livro representando uma pasta de trabalho
não salva com o nome Book1 (nome sem extensão de
arquivo).
xw.Book("Book1.xlsx") Retorna um livro objeto Book1.xlsx (nome com
extensão de arquivo). O arquivo deve estar aberto ou no
diretório de trabalho atual.
xw.Book(r"C:\path\Book1.xlsx") Retorna um livro objeto deO arquivo pode ser aberto
ou fechado. O r transforma a string em uma string bruta
para que as barras invertidas (\) do caminho sejam
interpretadas literalmente no Windows (introduzi as
strings brutas no Capítulo 5). No macOS, o r não é
necessário, pois os caminhos de arquivo usam barras em
vez de barras invertidas.
xw.books.active Retorna um livro que representa a pasta de trabalho
ativa na instância ativa do Excel.
Vamos ver como podemos percorrer a hierarquia do modelo de objeto do objeto book até o
objeto range:
In [4]: # Crie uma nova pasta de trabalho vazia e imprima seu nome. Este é o
# livro que usaremos para executar a maioria dos exemplos de código
neste capítulo.
book = xw.Book()
book.name
Out[4]: 'Book2'
In [5]: Acessando a coleção de planilhas
book.sheets
Out[5]: Sheets([<Sheet [Book2]Sheet1>])
In [6]: # Obtém um objeto de pasta por índice ou nome. Você precisará ajustar
# "Planilha1" se sua planilha for chamada de forma diferente.
Página 186 Capítulo 9
sheet1 = book.sheets[0]
sheet1 = book.sheets["Sheet1"]
In [7]: sheet1.range("A1")
Out[7]: <Range [Book2]Sheet1!$A$1>
Com o objeto range, chegamos ao fim da hierarquia. A string que é impressa entre
colchetes fornece informações úteis sobre esse objeto, mas para fazer algo, você
normalmente usa o objeto com um atributo, como mostra a próxima amostra:
In [8]: # Tarefas mais comuns: escrever valores. ..
sheet1.range("A1").value = [[1, 2],
[3, 4]]
sheet1.range("A4").value = "Hello!"
In [9]: # ...and read values
sheet1.range("A1:B2").value
Out[9]: [[1.0, 2.0], [3.0, 4.0]]
In [10]: sheet1.range("A4").value
Out[10]: 'Hello!'
Como você pode ver, por padrão, o atributo value de um objeto xlwings range aceita e
retorna uma lista aninhada para intervalos bidimensionais e um escalar para uma única célula.
Tudo o que usamos até agora é quase idêntico ao VBA: supondo que o book seja um objeto
de pasta de trabalho VBA ou xlwings, respectivamente, é assim que você acessa o value das
células A1 a B2 no VBA e com xlwings:
book.Sheets(1).Range("A1:B2").Value # VBA
book.sheets[0].range("A1:B2").value # xlwings
As diferenças são:
Atributos
Python usa letras minúsculas, potencialmente com sublinhados como sugerido pelo
PEP 8, o guia de estilo do Python que apresentei no Capítulo 3.
Indexação
Python usa colchetes e índices baseados em zero para acessar um elemento na coleção
sheets.
Tabela 9-2 dá-lhe uma visão geral das cordas que um xlwings range aceita.
Página 187 Capítulo 9
Tabela 9-2. Strings para definir um intervalo em notação A1
Referência Descrição
"A1" Uma única célula
"A1:B2" Células de A1 a
B2 "A:A" Coluna A
"A:B" Colunas A a B
"1:1" Linha 1
" 1:2" Linhas 1 a 2
Em vez de usar range explicitamente como um atributo do objeto sheet, você também
pode obter um objeto range indexando e fatiando o objeto sheet. Usar isso com notação
A1 permitirá que você digite menos, e usar isso com índices inteiros faz com que a planilha
do Excel pareça um array NumPy:
In [13]: # Single cell: A1 notation
sheet1["A1"]
Out[13]: <Range [Book2]Sheet1!$A$1>
In [14]: # Várias células: A1 notação
sheet1["A1:B2"]
Out[14]: <Range [Book2]Sheet1!$A$1:$B$2>
In [15]: # Célula única: indexação
sheet1[0, 0]
Out[15]: <Range [Book2]Sheet1!$A$1>
In [16]: # Várias células: fatia
sheet1[:2, :2]
Página 188 Capítulo 9
Out[16]: <Range [Book2]Sheet1!$A$1:$B$2>
Às vezes, no entanto, pode ser mais intuitivo definir um intervalo referindo-se à célula
superior esquerda e inferior direita de um intervalo. Os exemplos a seguir referem-se aos
intervalos de células D10 e D10:F11, respectivamente, permitindo que você entenda a
diferença entre indexar/fatiar um objeto sheet e trabalhando com um objeto range:
In [17]: # D10 via sheet indexing
sheet1[9, 3]
Out[17]: <Range [Book2]Sheet1!$D$10>
In [18]: # D10 via range object
sheet1.range((10, 4))
Out[18]: <Range [Book2]Sheet1!$D$10>
In [19]: # D10:F11 via sheet slicing
sheet1[9:11, 3:6]
Out[19]: <Range [Book2]Sheet1!$D$10:$F$11>
In [20]: # D10:F11 via range object
sheet1.range((10, 4), (11, 6))
Out[20]: <Range [Book2]Sheet1!$D$10:$F$11>
Definindo objetos range com tuplas é muito semelhante a como a propriedade Cells
funciona no VBA , como mostra a comparação a seguir — isso pressupõe novamente que o
book é um objeto de pasta de trabalho do VBA ou um objeto xlwings book. Vejamos
primeiro a versão do VBA:
With book.Sheets(1)
myrange = .Range(.Cells(10, 4), .Cells(11, 6))
End With
Tendo chegado ao topo do modelo de objeto do Excel, é um bom momento para ver como
você pode trabalhar com várias instâncias do Excel. Você precisará usar o objeto app
explicitamente se você deseja abrir a mesma pasta de trabalho em várias instâncias do Excel
ou se deseja distribuir especificamente suas pastas de trabalho em diferentes instâncias por
motivos de desempenho. Outro caso de uso comum para trabalhar com um objeto app é
abrir sua pasta de trabalho em uma instância oculta do Excel: isso permite que você execute
um script xlwings em segundo plano sem impedir que você faça outro trabalho no Excel nesse
meio tempo:
In [22]: # Obtenha um objeto de aplicativo da pasta de trabalho aberta
# e crie uma instância de aplicativo invisível adicional
visible_app = sheet1.book.app
invisible_app = xw.App(visible=False)
In [23]: # Liste os nomes de livros que estão abertos em cada instância
# usando uma compreensão de lista
[book.name for book in visible_app.books]
Out[23]: ['Book1', 'Book2']
In [24]: [book.name for book in invisible_app.books]
Out[24]: ['Book3']
In [25]: # An app key represents the process ID (PID)
xw.apps.keys()
Out[25]: [5996, 9092]
In [26]: # It can also be accessed via the pid attribute
xw.apps.active.pid
Out[26]: 5996
In [27]: # Trabalhar com o livro na instância invisível do Excel
invisible_book = invisible_app.books[0]
invisible_book.sheets[0]["A1"].value = "Created by an invisible app."
Se você tiver a mesma pasta de trabalho aberta em duas instâncias do Excel ou se desejar
especificar em qual instância do Excel deseja abrir uma pasta de trabalho, não poderá mais
usar xw.Book não mais. Em vez disso, você precisa usar a coleção books conforme
apresentado na Tabela 9-3. Observe que myapp significa um objeto xlwings app. Se você
substituir myapp.books por xw.books em vez disso, xlwings usará o ativo app.
Antes de nos aprofundarmos em como o xlwings pode substituir suas macros VBA, vamos
ver como o xlwings pode interagir com seu código VBA existente: isso pode ser útil se você
tiver muito código legado e não tiver tempo para migrar tudo para o Python.
Para chamar essas funções via Python, primeiro você precisade objeto xlwings macro que
você chamará posteriormente, fazendo parecer como se fosse uma função nativa do Python:
In [30]: vba_book = xw.Book("xl/vba.xlsm")
Agora que você sabe como interagir com o código VBA existente, podemos continuar nossa
exploração do xlwings analisando como usá-lo com DataFrames, arrays NumPy e coleções
como gráficos, imagens e nomes definidos.
Se, no entanto, você deseja suprimir os cabeçalhos das colunas e/ou o índice, use o método
options assim:
In [36]: sheet1["B10"].options(header=False, index=False).value = df
A leitura de intervalos do Excel como DataFrames exige que você forneça a classe
DataFrame como o parâmetro convert do método options. Por padrão, ele espera que
seus dados tenham um cabeçalho e um índice, mas você pode usar novamente os parâmetros
index e header para alterar isso. Em vez de usar o conversor, você também pode ler os
valores primeiro como uma lista aninhada e, em seguida, construir manualmente seu
DataFrame, mas usar o conversor torna um pouco mais fácil lidar com o índice e o
cabeçalho.
O método expand
No exemplo de código a seguir, estou apresentando o expand que facilita a leitura
de um bloco contíguo de células, fornecendo o mesmo intervalo como se você
estivesse fazendo Shift+Ctrl+Down-Arrow+Right-Arrow no Excel, exceto que
expand salta sobre uma célula vazia no canto superior esquerdo.
Conversores e Opções
Como acabamos de ver, o método options do objeto xlwings range permite que você
influencie a maneira como os valores são lidos e gravados no Excel. Aquilo é, options são
avaliados apenas quando você chama o atributo value em um objeto range. A sintaxe é a
seguinte (myrange é um objeto xlwings range):
myrange.options(convert=None, option1=value1, option2=value2, ...).value
Tabela 9-4 mostra os conversores internos, ou seja, os valores que o argumento convert
aceita. Eles são chamados de integrados, pois o xlwings oferece uma maneira de escrever
seus próprios conversores, o que pode ser útil se você precisar aplicar repetidamente
transformações adicionais antes de escrever ou depois de ler valores - para ver como
funciona, dê uma olhada no xlwings documentos.
Já usamos as opções index e header com o exemplo DataFrame, mas há mais opções
disponíveis, conforme mostrado na Tabela 9-5.
Página 194 Capítulo 9
Tabela 9-5. Opções integradas
Opção Descrição
empty Por padrão, as células vazias são lidas como None. Altere isso fornecendo um valor
para empty.
date Aceita uma função que é aplicada a valores de células formatadas por data.
number Aceita uma função aplicada a números.
ndim Número de dimensões: ao ler, use ndim para forçar os valores de um intervalo a
chegar em uma determinada dimensionalidade. Deve ser None, 1 ou 2. Pode ser
usado ao ler valores como listas ou matrizes NumPy.
transpose Transpõe os valores, ou seja, transforma as colunas em linhas ou vice-versa.
index Para ser usado com pandas DataFrames e Series: ao ler, use-o para definir se o
intervalo do Excel contém o índice. Pode ser True/False ou um número inteiro. O
inteiro define quantas colunas devem ser transformadas em um MultiIndex. Por
exemplo, 2 usará as duas colunas mais à esquerda como índice. Ao escrever, você
pode decidir se deseja gravar o índice definindo index como True ou False.
header Funciona da mesma forma que index, mas aplicado aos cabeçalhos das colunas.
Vamos dar uma olhada em ndim: por padrão, quando você lê em uma única célula do Excel,
você obterá um escalar (por exemplo, um float ou uma string); quando você lê em uma
coluna ou linha, obtém uma lista simples; e, finalmente, quando você lê em um intervalo
bidimensional, obtém uma lista aninhada (ou seja, bidimensional). Isso não é apenas
consistente em si, mas também é equivalente a como o fatiamento funciona com matrizes
NumPy, como visto no Capítulo 4. O caso unidimensional é especial: às vezes, uma coluna
pode ser apenas um caso extremo do que de outra forma é um intervalo bidimensional.
Nesse caso, faz sentido forçar um intervalo a sempre chegar como uma lista bidimensional
usando ndim=2:
In [40]: # Faixa horizontal (unidimensional)
sheet1["A1:B1"].value
Out[40]: [1.0, 2.0]
In [41]: # Faixa vertical (unidimensional)
sheet1["A1:A2"].value
Out[41]: [1.0, 3.0]
In [42]: # Faixa horizontal (bidimensional)
sheet1["A1:B1"].options(ndim=2).value
Out[42]: [[1.0, 2.0]]
In [43]: # Faixa vertical (bidimensional)
sheet1["A1:A2"].options(ndim=2).value
Out[43]: [[1.0], [3.0]]
In [44]: # Usando o conversor de matriz NumPy se comporta da mesma forma:
# alcance vertical leva a um array unidimensional
sheet1["A1:A2"].options(np.array).value
Out[44]: array([1., 3.])
In [45]: # Preservando a orientação da coluna
sheet1["A1:A2"].options(np.array, ndim=2).value
Página 195 Capítulo 9
Out[45]: array([[1.],
[3.]])
In [46]: # Se você precisa escrever uma lista verticalmente,
# a opção "transpose" vem a calhar
sheet1["D1"].options(transpose=True).value = [100, 200]
Use ndim=1 para forçar o valor de uma única célula a ser lido como uma lista em vez de um
escalar. Você não precisará ndim com pandas, pois um DataFrame é sempre bidimensional
e uma série é sempre unidimensional. Aqui está mais um exemplo mostrando como as
opções funcionam empty, date, e number:
In [47]: # Escreva alguns dados de exemplo
sheet1["A13"].value = [dt.datetime(2020, 1, 1), None, 1.0]
Até agora, trabalhamos com os objetos book, sheet e range. Vamos agora aprender como
lidar com coleções como gráficos que você acessa a partir do objeto sheet!
Gráficos do Excel
Para adicionar um novo gráfico, use o método add de coleção charts, e, em seguida, defina
o tipo de gráfico e os dados de origem:
In [50]: sheet1["A15"].value = [[None, "North", "South"],
["Last Year", 2, 5],
["This Year", 3, 6]]
4 Outra coleção popular é tables. Para usá-los, você precisa de pelo menos xlwings 0.21.0; veja os documentos.
Página 196 Capítulo 9
In [51]: chart = sheet1.charts.add(top=sheet1["A19"].top,
left=sheet1["A19"].left)
chart.chart_type = "column_clustered"
chart.set_source_data(sheet1["A15"].expand())
Isso produzirá o gráfico mostrado no lado esquerdo da Figura 9-2. Para pesquisar os tipos
de gráficos disponíveis, consulte os documentos xlwings. Se você gosta mais de trabalhar
com gráficos de pandas do que com gráficos do Excel, ou se deseja usar um tipo de gráfico
que não está disponível no Excel, o xlwings o cobre - vamos ver como!
Para atualizar a imagem com um novo gráfico, basta usar o método update com outro
objeto figure — tecnicamente, isso substituirá a imagem no Excel, mas preservará todas
as propriedades, como local, tamanho e nome:
In [56]: ax = (df + 1).T.plot.bar()
plot = plot.update(ax.get_figure())
Página 197 Capítulo 9
Figura 9-2 mostra como o gráfico do Excel e o gráfico do Matplotlib se comparam após a
chamada update.
Gráficos e imagens são coleções que são acessadas por meio de um objeto sheet. Nomes
definidos, a coleção que veremos a seguir, podem ser acessados a partir do objeto sheet ou
o book. Vamos ver que diferença isso faz!
Definidos Nomes
No Excel, você cria um nome definido atribuindo um nome a um intervalo, uma fórmula
ou uma constante.5 Atribuir um nome a um intervalo é provavelmente o caso mais comum
e chamado de intervalo nomeado. Com um intervalo nomeado, você pode fazer referência
ao intervalo do Excel em fórmulas e código usando um nome descritivo em vez de um
endereço abstrato na forma de A1:B2. Usá-los com xlwings torna seu código mais flexível e
sólido: ler e escrever valores de e para intervalos nomeados oferece a flexibilidade de
reestruturar sua pasta de trabalho sem precisar ajustar seu código Python: um nome fica na
célula, mesmo se você o mover inserindo uma nova linha, por exemplo.
5 Nomes definidos com fórmulas também são usado para funções lambda, uma nova maneira de definir funções
definidas pelo usuário sem VBA ou JavaScript, que a Microsoft anunciou como um novo recurso para assinantes
do Microsoft 365 em dezembro de 2020.
Página 198 Capítulo 9
Os nomes definidos podem ser definidos no escopo do livro global ou no escopo da planilha
local. A vantagem de um nome com escopo de planilha é que você pode copiar a planilha
sem entrar em conflito com intervalos nomeados duplicados. No Excel, você adiciona
nomes definidos manualmente acessando Fórmulas > Definir nome ou selecionando um
intervalo e, em seguida, escrevendo o nome desejado na caixa de nome - esta é a caixa de
texto à esquerda da barra de fórmulas, onde você vê o endereço da célula por padrão. Aqui
está como você gerencia nomes definidos com xlwings:
In [57]: # O escopo do livro é o escopo padrão
sheet1["A1:B2"].name = "matrix1"
In [58]: # Para o escopo da planilha, coloque o nome da planilha com
# um ponto de exclamação
sheet1["B10:E11"].name = "Sheet1!matrix2"
In [59]: # Agora você pode acessar o intervalo pelo nome
sheet1["matrix1"]
Out[59]: <Range [Book2]Sheet1!$A$1:$B$2>
In [60]: # Se você acessar a coleção de nomes por meio do objeto "sheet1",
# ele conterá apenas nomes com o escopo
sheet1.names
Out[60]: [<Name 'Sheet1!matrix2': =Sheet1!$B$10:$E$11>]
In [61]: # Se você acessar a coleção de nomes através do objeto "book",
# contém todos os nomes, incluindo escopo de livro e planilha
book.names
Out[61]: [<Name 'matrix1': =Sheet1!$A$1:$B$2>, <Name 'Sheet1!matrix2':
=Sheet1!$B$10:$E$11>]
Dê uma olhada nos nomes definidos gerados no Excel abrindo o Name Manager via Formulas
> Name Manager (veja a Figura 9- 3). Observe que o Excel no macOS não possui um
Gerenciador de Nomes — em vez disso, vá para Fórmulas > Definir Nome, de onde você verá
os nomes existentes.
Página 199 Capítulo 9
Figura 9-3. Gerenciador de Nomes do Excel após adicionar alguns nomes definidos via
xlwings
Neste ponto, você sabe como trabalhar com os componentes mais usados de uma pasta de
trabalho do Excel. Isso significa que podemos examinar o estudo de caso de relatórios do
Capítulo 7 mais uma vez: vamos ver o que muda quando colocamos xlwings em cena!
Ao executar este script pela primeira vez no macOS (por exemplo, abrindo-o no VS Code e
clicando no botão Executar arquivo), você terá que confirmar novamente um pop-up para
conceder acesso ao sistema de arquivos, algo que já vimos anteriormente neste capítulo.
Com modelos do Excel formatados, você pode criar belos relatórios do Excel muito
rapidamente. Você também tem acesso a métodos como autofit, algo que não está
disponível com os pacotes writer, pois depende de cálculos feitos pelo aplicativo Excel: isso
permite definir corretamente a largura e a altura de suas células de acordo com seu conteúdo.
Figura 9-4 mostra a parte superior do relatório de vendas gerado pelo xlwings com um
cabeçalho de tabela personalizado, bem como as colunas onde o método autofit foi
aplicado.
Quando você começa a usar o xlwings para mais do que apenas preencher algumas células
em um modelo, é bom saber um pouco sobre seus componentes internos: a próxima seção
analisa como o xlwings funciona sob o capô.
Página 201 Capítulo 9
xlwings Foundations
xlwings depende de outros pacotes Python para se comunicar com o mecanismo de
automação do respectivo sistema operacional:
Windows
No Windows, xlwings depende da tecnologia COM, abreviação de Component Object
Model. COM é um padrão que permite que dois processos se comuniquem - no nosso
caso Excel e Python. xlwings usa o pacote Python pywin32 para lidar com as chamadas
COM.
macOS
No macOS, o xlwings depende do AppleScript. AppleScript é a linguagem de script da
Apple para automatizar aplicativos programáveis — felizmente, o Excel é um aplicativo
programável. Para executar comandos AppleScript, xlwings usa o pacote Python
appscript.
Página 202 Capítulo 9
Windows: como prevenir processos zumbis
Quando você brinca com xlwings no Windows, às vezes você notará que o Excel parece estar
completamente fechado, mas quando você abre o Gerenciador de Tarefas (clique com o
botão direito do mouse na barra de tarefas do Windows e selecione Gerenciador de Tarefas),
você verá o Microsoft Excel em Processos em segundo plano na guia Processos. Se você não
vir nenhuma guia, clique primeiro em “Mais detalhes”. Como alternativa, vá para a guia
Detalhes, onde você verá o Excel listado como “EXCEL.EXE”. Para encerrar um processo
zumbi, clique com o botão direito do mouse na respectiva linha e selecione “Finalizar tarefa”
para forçar o fechamento do Excel.
Como esses processos são undead em vez de terminados adequadamente, eles são
frequentemente chamados de processos zumbis. Deixá-los de lado usa recursos e pode levar
a um comportamento indesejado: por exemplo, os arquivos podem ser bloqueados ou os
suplementos podem não ser carregados corretamente quando você abre uma nova instância
do Excel. A razão pela qual o Excel às vezes não consegue desligar corretamente é que os
processos só podem ser encerrados quando não há mais referências COM, por exemplo, na
forma de um objeto xlwings app. Mais comumente, você acaba com um processo zumbi
do Excel depois de matar o interpretador Python, pois isso impede que ele limpe
adequadamente as referências COM. Considere este exemplo em um prompt do Anaconda:
(base)> python
>>> import xlwings as xw
>>> app = xw.App()
Quando a nova instância do Excel estiver em execução, encerre-a novamente por meio da
interface do usuário do Excel: enquanto o Excel fecha, o processo do Excel no Gerenciador
de Tarefas continuará em execução. Se você encerrar a sessão do Python corretamente
executando quit() ou usando o atalho Ctrl+Z, o processo do Excel acabará por ser
encerrado. Se, no entanto, você matar o Anaconda Prompt clicando no “x” no canto
superior direito da janela, você notará que o processo permanece como um processo zumbi.
O mesmo acontece se você matar o Prompt do Anaconda antes de fechar o Excel ou se você
o matar enquanto estiver executando um servidor Jupyter e você mantiver um objeto
xlwings app em uma das células do notebook Jupyter. Para minimizar as chances de acabar
com processos zumbis do Excel, aqui estão algumas sugestões:
Melhorando o desempenho
Para manter o desempenho de seus scripts xlwings, existem algumas estratégias: a mais
importante é manter as chamadas entre aplicativos em um mínimo absoluto. Usar valores
brutos pode ser outra opção e, finalmente, definir as propriedades corretas app também
pode ajudar. Vamos passar por essas opções uma após a outra!
Esses números são ainda mais extremos no macOS, onde a segunda opção é cerca de 50 vezes
mais rápida que a primeira na minha máquina.
Valores brutos
O xlwings foi projetado principalmente com foco na conveniência e não na velocidade. No
entanto, se você lidar com grandes intervalos de células, poderá se deparar com situações em
que pode economizar tempo pulando a etapa de limpeza de dados do xlwings: xlwings
percorre cada valor quando você lê e grava dados, por exemplo, para alinhar tipos de dados
entre Windows e Mac OS. Usando a string raw como conversor no método options, você
pular esta etapa. Embora isso deva tornar todas as operações mais rápidas, a diferença pode
não ser significativa, a menos que você grave matrizes grandes no Windows. Usar valores
brutos, no entanto, significa que você não pode mais trabalhar diretamente com
DataFrames.
Página 204 Capítulo 9
Em vez disso, você precisa fornecer seus valores como listas ou tuplas aninhadas. Além disso,
você precisará fornecer o endereço completo do intervalo para o qual está escrevendo —
desde que a célula superior esquerda não seja mais suficiente:
In [67]: # Com valores brutos, você deve fornecer o
# intervalo de destino completo, folha ["A35"] não funciona mais
sheet1["A35:B36"].options("raw").value = [[1, 2], [3, 4]]
Propriedades do aplicativo
Dependendo do conteúdo de sua pasta de trabalho, alterar as propriedades de seus objetos
app também pode ajudar a tornar o código mais rápido. Normalmente, você deseja ver as
seguintes propriedades (myapp é um objeto xlwings app):
• myapp.screen_updating = False
• myapp.calculation = "manual"
• myapp.display_alerts = False
• Verifique se o método está disponível no objeto xlwings range, por exemplo, usando a
tecla Tab depois de colocar um ponto no final de um objeto range em um notebook
Jupyter, executando dir(sheet["A1"]) ou pesquisando o xlwings API reference. No
VS Code, os métodos disponíveis devem ser mostrados automaticamente em uma dica
de ferramenta.
• Se a funcionalidade desejada estiver ausente, use o atributo api para obter o objeto
subjacente: no Windows, sheet["A1"].api fornecerá um objeto pywin32 e no
macOS, você obterá um objeto appscript.
Página 205 Capítulo 9
• Verifique o modelo de objeto do Excel na referência do Excel VBA. Para limpar o
formato de um intervalo, você terminaria em Range.ClearFormats.
• No Windows, na maioria dos casos, você pode usar o método VBA ou propriedade
diretamente com seu objeto api. Se for um método, certifique-se de adicionar
parênteses em Python: sheet["A1"].api.ClearFormats(). Se você estiver fazendo
isso no macOS, as coisas são mais complicadas, pois o appscript usa uma sintaxe que
pode ser difícil de adivinhar. Sua melhor abordagem é consultar o guia do
desenvolvedor que faz parte da fonte xlwings código. Limpar a formatação da célula,
no entanto, é bastante fácil: basta aplicar as regras de sintaxe do Python no nome do
método usando caracteres minúsculos com sublinhados:
sheet["A1"].api.clear_formats().
Se você precisa ter certeza de que o ClearFormats funciona em ambas as plataformas, você
pode fazê-lo da seguinte forma (darwin é o núcleo do macOS e usado como seu nome por
sys.platform):
import sys
if sys.platform.startswith("darwin"):
sheet["A10"].api.clear_formats()
elifsys.platform.startswith("win"):
sheet["A10"].api.ClearFormats()
De qualquer forma, vale a pena abrir um problema no repositório GitHub para ter a
funcionalidade incluída em uma versão futura.
Conclusão
Este capítulo apresentou o conceito de automação do Excel: via xlwings, você pode usar o
Python para tarefas que tradicionalmente faria no VBA. Aprendemos sobre o modelo de
objeto do Excel e como o xlwings permite que você interaja com seus componentes, como
os objetos sheet e range. Equipados com esse conhecimento, voltamos ao estudo de caso
de relatórios do Capítulo 7 e usamos xlwings para preencher um modelo de relatório pré-
formatado; isso mostrou a você que existe um caso para usar os pacotes do leitor e xlwings
lado a lado. Também aprendemos sobre as bibliotecas que o xlwings usa nos bastidores para
entender como podemos melhorar o desempenho e contornar a funcionalidade ausente.
Meu recurso xlwings favorito é que ele funciona tão bem no macOS quanto no Windows.
Isso é ainda mais emocionante, pois o Power Query no macOS ainda não possui todos os
recursos da versão do Windows: o que estiver faltando, você poderá substituí-lo facilmente
por uma combinação de pandas e xlwings.
Agora que você conhece o xlwings básico, você está pronto para o próximo capítulo: lá,
vamos dar o próximo passo e chamar os scripts xlwings do próprio Excel, permitindo que
você construa ferramentas do Excel baseadas em Python.
Página 206
CAPÍTULO 10
Ferramentas do Excel baseadas em
Python
Você precisará manter a versão do pacote Python e a versão do add-in em sincronia sempre
que atualizar o xlwings. Portanto, você deve sempre executar dois comandos ao atualizar o
xlwings — um para o pacote Python e outro para o suplemento do Excel. Dependendo se
você usa o gerenciador de pacotes Conda ou pip, é assim que você atualiza sua instalação
xlwings:
Conda (use isso com a distribuição Anaconda Python)
(base)> conda update xlwings
(base)> xlwings addin install
pip (use isso com qualquer outra distribuição Python)
(base)> pip install --upgrade xlwings
(base)> xlwings addin install
Software antivírus
Infelizmente, o suplemento xlwings às vezes é sinalizado como um suplemento
malicioso pelo software antivírus, especialmente se você estiver usando um
lançamento totalmente novo. Se isso acontecer em sua máquina, vá para as
configurações do seu software antivírus, onde você poderá marcar o xlwings como
seguro para ser executado. Normalmente, também é possível relatar esses falsos
positivos por meio da página inicial do software.
Ao digitar xlwings em um prompt do Anaconda, você está usando a CLI xlwings. Além
de facilitar a instalação do add-in xlwings, ele oferece mais alguns comandos: Vou apresentá-
los sempre que precisarmos, mas você sempre pode digitar xlwings em um prompt do
Anaconda e pressionar Enter para imprimir as opções disponíveis. Vamos agora dar uma
olhada no que xlwings addin install faz:
Instalação
A instalação real do add-in é feita copiando xlwings.xlam do diretório do pacote
Python para a pasta do Excel XLSTART, que é uma pasta especial: Excel irá abrir todos
os arquivos que estão nesta pasta toda vez que você inicia o Excel. Quando você executa
xlwings addin status em um prompt do Anaconda, ele imprimirá
Página 208 Capítulo 10
onde o diretório XLSTART está em seu sistema e se o add-in está instalado ou não.
Configuração
Ao instalar o suplemento pela primeira vez, ele também o configurará para usar o
interpretador Python ou o ambiente Conda de onde você está executando o comando
install: como você vê na Figura 10-1, os valores para Conda Path e Conda Env são
preenchidos automaticamente pela CLI xlwings.1 Esses valores são armazenados em um
arquivo chamado xlwings.conf na pasta .xlwings em seu diretório pessoal. No
Windows, geralmente é C:\Users\<username>\.xlwings\xlwings.conf e no macOS
/Users/<username>/.xlwings/xlwings.conf. No macOS, pastas e arquivos com um
ponto inicial ficam ocultos por padrão. Quando estiver no Finder, digite o atalho de
teclado Command-Shift-. para alternar sua visibilidade.
Após executar o comando de instalação, você terá que reiniciar o Excel para ver a guia
xlwings na faixa de opções, conforme mostrado na Figura 10-1.
no macOS
No macOS, a faixa de opções parece um pouco diferente, pois faltam as seções
sobre funções definidas pelo usuário e Conda: enquanto as funções definidas pelo
usuário não são suportadas no macOS , os ambientes Conda não requerem
tratamento especial, ou seja, são configurados como Interpreter no grupo Python.
Agora que você tem o suplemento xlwings instalado, precisaremos de uma pasta de trabalho
e algum código Python para testá-lo. A maneira mais rápida de chegar lá é usando o
comando quickstart, como mostrarei a seguir.
1 Se você estiver no macOS ou usando uma distribuição Python diferente do Anaconda, ele configurará o Interpreter
em vez das configurações do Conda.
Página 209 Capítulo 10
Comando Quickstart
Para facilitar ao máximo a criação de sua primeira ferramenta xlwings, a CLI xlwings oferece
o comando quickstart. Em um prompt do Anaconda, use o cd para mudar para o
diretório onde você deseja criar seu primeiro projeto (por exemplo, cd Desktop), então
execute o seguinte para criar um projeto com o nome first_project:
(base)> xlwings quickstart first_project
O nome do projeto deve ser um nome de módulo Python válido: ele pode conter caracteres,
números e sublinhados, mas sem espaços ou traços, e não deve começar com um número.
Mostrarei em “Função RunPython” na página 213 como você pode alterar o nome do
arquivo Excel para algo que não precisa seguir essas regras. A execução do comando
quickstart criará uma pasta chamada first_project em seu diretório atual. Ao abri-lo no
Explorador de Arquivos no Windows ou no Finder no macOS, você verá dois arquivos:
first_project.xlsm e first_project.py. Abra os dois arquivos — o arquivo Excel no Excel e
o arquivo Python no VS Code. A maneira mais fácil de executar o código Python do Excel
é usando o botão principal Executar no suplemento — vamos ver como funciona!
Run Main
Antes de examinar first_project.py com mais detalhes, vá em frente e clique no botão Run
main à esquerda do add-in xlwings enquanto first_project.xlsm é seu arquivo ativo; ele
escreverá “Olá xlwings!” na célula A1 da primeira folha. Clique no botão novamente e ele
mudará para “Tchau xlwings!” Parabéns, você acabou de executar sua primeira função
Python do Excel! Afinal, isso não era muito mais difícil do que escrever uma macro VBA,
era? Vamos agora dar uma olhada em first_project.py no Exemplo 10-1.
def main():
wb = xw.Book.caller()
sheet = wb.sheets[0]
if sheet["A1"].value == "Hello xlwings!":
sheet["A1"].value = "Bye xlwings!"
else:
sheet["A1"].value = "Hello xlwings!"
@xw.func
def hello(name):
return f"Hello {name}!"
Página 210 Capítulo 10
if __name__ == " main ":
xw.Book("first_project.xlsm").set_mock_caller()
main()
Neste capítulo, vamos ignorar a função hello, pois este será o tópico do Capítulo 12.
Se você executar o comando quickstart no macOS, não verá a função hello de
qualquer maneira, pois as funções definidas pelo usuário são suportadas apenas no
Windows.
Função RunPython
Se você precisar de mais controle sobre como chamar seu código Python, use a função VBA
RunPython. Consequentemente, RunPython requer que sua pasta de trabalho seja salva
como uma pasta de trabalho habilitada para macro.
Habilitar Macros
Você precisa clicar em Habilitar Conteúdo (Windows) ou Habilitar Macros
(macOS) ao abrir uma pasta de trabalho habilitada para macro (extensão xlsm),
como aquela gerada pelo comando quickstart. No Windows, ao trabalhar com
arquivos xlsm do repositório complementar, você deve clicar adicionalmente em
Habilitar edição ou o Excel não abrirá os arquivos baixados da Internet
corretamente.
Página 211 Capítulo 10
RunPython aceita uma string com código Python: mais comumente, você importa um
módulo Python e executa uma de suas funções. Ao abrir o editor VBA via Alt+F11
(Windows) ou Option-F11 (macOS), você verá que o comando quickstart adiciona uma
macro chamada SampleCall em um módulo VBA com o nome “Module1” (veja a Figura
10-2) . Se você não vir o SampleCall, clique duas vezes em Module1 na árvore do projeto
VBA no lado esquerdo.
O código parece um pouco complicado, mas isso é apenas para fazê-lo funcionar
dinamicamente, independentemente do nome do projeto escolhido ao executar o comando
quickstart. Como nosso módulo Python é chamado first_project, você pode
substituir o código pelo seguinte equivalente fácil de entender:
Sub SampleCall()
RunPython "import first_project; first_project.main()"
End Sub
Como não é divertido escrever strings de várias linhas em VBA , usamos um ponto e vírgula
que o Python aceita em vez de uma quebra de linha. Existem algumas maneiras de executar
esse código: por exemplo, enquanto estiver no editor VBA, coloque o cursor em qualquer
linha da macro SampleCall e pressione F5. Normalmente, no entanto, você estará
executando o código de uma planilha do Excel e não do editor VBA. Portanto, feche o
editor VBA e volte para a pasta de trabalho. Digitar Alt+F8 (Windows) ou Option-F8
(macOS) abrirá o menu macro: selecione SampleCall e clique no botão Executar. Ou, para
torná-lo mais fácil de usar, adicione um botão à sua pasta de trabalho do Excel e conecte-o
ao SampleCall: primeiro, certifique-se de que a guia Desenvolvedor na faixa de opções seja
exibida. Se não for, vá para File > Opções > Personalizar Faixa de Opções e ative a caixa de
seleção ao lado de Desenvolvedor (no macOS, você a encontrará em Excel > Preferências >
Faixa de Opções e Barra de Ferramentas). Para inserir um botão, vá para a guia
Desenvolvedor e no grupo Controles, clique em Inserir > Botão (em Controles de
formulário). No macOS, você verá o botão sem precisar acessar Inserir primeiro. Quando
você clica no ícone do botão, seu cursor se transforma em uma pequena cruz: use-o para
desenhar um botão em sua planilha mantendo o botão esquerdo do mouse pressionado
enquanto desenha uma forma retangular. Depois de soltar o botão do mouse, você verá o
menu Assign Macro - selecione SampleCall e clique em OK.
Página 212 Capítulo 10
Clique no botão que você acabou de criar (no meu caso é chamado de “Botão 1”), e ele
executará nossa função main novamente, como na Figura 10-3.
Agora vamos dar uma olhada em como podemos alterar os nomes padrão que foram
atribuídos pelo comando quickstart: volte ao seu arquivo Python e renomeie-o de
first_project.py para hello.py. Além disso, renomeie sua função main para hello_world.
Certifique-se de salvar o arquivo e, em seguida, abra o editor VBA novamente via Alt+F11
(Windows) ou Option-F11 (macOS) e edite SampleCall da seguinte forma para refletir as
alterações:
Sub SampleCall()
RunPython "import hello; hello.hello_world()"
End Sub
De volta à planilha, clique no “Botão 1” para certificar-se de que tudo ainda funciona.
Finalmente, você também pode querer manter o script Python e o arquivo Excel em dois
diretórios diferentes. Para entender as implicações disso, primeiro preciso dizer uma palavra
sobre o caminho de pesquisa do módulo: se você importar um módulo em seu código, o
Python o procurará em vários diretórios. Primeiro, o Python verifica se existe um módulo
interno com esse nome e, se não encontrar um, passa a procurar no diretório de trabalho
atual e nos diretórios fornecidos pelo chamado PYTHONPATH.
Página 213 Capítulo 10
xlwings adiciona automaticamente o diretório da pasta de trabalho ao PYTHONPATH e
permite adicionar caminhos adicionais por meio do suplemento. Para experimentar isso,
pegue o script Python que agora é chamado hello.py e mova-o para uma pasta chamada
pyscripts que você cria em seu diretório inicial: no meu caso, seria C:\Users\felix\pyscripts
no Windows ou /Users/felix/pyscripts no macOS. Ao clicar no botão novamente, você
receberá o seguinte erro em um pop-up:
Traceback (most recent call last):
File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'first_project'
Para corrigir isso, basta adicionar o caminho do diretório pyscripts à PYTHONPATH em sua
faixa de opções xlwings, como na Figura 10-4. Quando você clicar no botão mais uma vez,
ele funcionará novamente.
O que eu ainda não toquei é o nome da pasta de trabalho do Excel: uma vez que sua chamada
de função RunPython usa um nome de módulo explícito como first_project em vez
do código que foi adicionado por quickstart, você está livre para renomear sua pasta de
trabalho do Excel qualquer coisa você quer.
Contar com o comando quickstart é a maneira mais fácil se você iniciar um novo projeto
xlwings. No entanto, se você tiver uma pasta de trabalho existente, talvez prefira configurá-
la manualmente. Vamos ver como é feito!
3. Adicionar uma referência a xlwings: RunPython é uma função que faz parte do
suplemento xlwings. Para usá-lo, você precisará certificar-se de ter uma referência
definida para xlwings em seu projeto VBA. Novamente, comece selecionando a pasta
de trabalho correta na visualização em árvore no lado esquerdo do editor VBA, então
vá para Tools > Reference e ative a caixa de seleção para xlwings, como visto na Figura
10-6.
Sua pasta de trabalho agora está pronta para ser usada com a chamada RunPython
novamente. Uma vez que tudo funciona em sua máquina, o próximo passo geralmente é
fazê-lo funcionar na máquina do seu colega – vamos ver algumas opções para tornar essa
parte mais fácil!
Página 215 Capítulo 10
Implantação
No desenvolvimento de software, o termo deployment refere-se à distribuição e instalação
de software para que os usuários finais possam usá-lo. No caso das ferramentas xlwings,
ajuda saber quais dependências são necessárias e quais configurações podem facilitar a
implantação. Vou começar com a dependência mais importante, que é o Python, antes de
examinar as pastas de trabalho que foram configuradas no modo autônomo para se livrar
do suplemento xlwings do Excel. Concluirei esta seção analisando mais de perto como a
configuração funciona com o xlwings.
Dependência do Python
Para poder executar as ferramentas xlwings, seus usuários finais devem ter uma instalação
do Python. Mas só porque eles ainda não têm Python não significa que não existam
maneiras de facilitar o processo de instalação. Aqui estão algumas opções:
Anaconda ou WinPython
Instrua seus usuários a baixar e instalar a distribuição Anaconda. Para estar no lado
seguro, você teria que concordar com uma versão específica do Anaconda para garantir
que eles estejam usando as mesmas versões dos pacotes contidos que você está usando.
Esta é uma boa opção se você usar apenas pacotes que fazem parte do Anaconda.
WinPy-thon é uma alternativa interessante ao Anaconda, pois é distribuído sob a
licença de código aberto do MIT e também vem com o xlwings pré-instalado. Como o
nome sugere, está disponível apenas no Windows.
Página 216 Capítulo 10
Unidade compartilhada
Se você tiver acesso a uma unidade compartilhada razoavelmente rápida, poderá instalar
o Python diretamente nela, o que permitirá que todos usem as ferramentas sem uma
instalação local do Python.
Executável congelado
No Windows, o xlwings permite trabalhar com executáveis congelados, que são
arquivos com a extensão .exe que contém Python e todas as dependências. Um pacote
popular para produzir executáveis congelados é o PyInstaller. Os executáveis
congelados têm a vantagem de empacotar apenas o que seu programa precisa e podem
produzir um único arquivo, o que pode facilitar a distribuição. Para mais detalhes sobre
como trabalhar com executáveis congelados, dê uma olhada na documentação do
xlwings. Observe que os executáveis congelados não funcionarão quando você usar
xlwings para funções definidas pelo usuário, a funcionalidade que apresentarei no
Capítulo 12.
Embora o Python seja um requisito difícil, a instalação do suplemento xlwings não é, como
explicarei a seguir.
Tendo abordado o Python e o add-in, vamos agora dar uma olhada mais detalhada em como
funciona a configuração do xlwings.
Hierarquia de configuração
Conforme mencionado no início deste capítulo, a faixa de opções armazena sua
configuração no diretório inicial do usuário, em .xlwings\xlwings.conf. A configuração
consiste em configurações individuais, como a PYTHONPATH que já vimos no início deste
capítulo. As configurações definidas em seu suplemento podem ser substituídas no nível do
diretório e da pasta de trabalho — o xlwings procura as configurações nos seguintes locais e
ordem:
Configuração da pasta de trabalho
Primeiro, o xlwings procura uma planilha chamada xlwings.conf. Essa é a maneira
recomendada de configurar sua pasta de trabalho para implantação, pois você não
precisa lidar com uma configuração de arquivo adicional. Quando você executa o
comando quickstart, ele cria uma configuração de amostra em uma planilha
chamada “_xlwings.conf”: remova o sublinhado inicial no nome para ativá-lo. Se você
não quiser usá-lo, sinta-se à vontade para excluir a planilha.
Configuração do diretório
Em seguida, o xlwings procura um arquivo chamado xlwings.conf no mesmo diretório
que sua pasta de trabalho do Excel.
Configuração do usuário
Finalmente, o xlwings procura um arquivo chamado xlwings.conf na pasta .xlwings
no diretório inicial do usuário. Normalmente, você não edita esse arquivo diretamente
— em vez disso, ele é criado e editado pelo suplemento sempre que você altera uma
configuração.
Se o xlwings não encontrar nenhuma configuração nesses três locais, ele retornará aos
valores padrão.
Ao editar as configurações por meio do suplemento do Excel, ele editará automaticamente
o arquivo xlwings.conf. Se você quiser editar o arquivo diretamente, procure o formato
exato e as configurações disponíveis acessando xlwings docs, mas a seguir indicarei as
configurações mais úteis no contexto de implantação.
Página 218 Capítulo 10
Configurações
A configuração mais crítica é certamente o interpretador Python — se sua ferramenta Excel
não encontrar o interpretador Python correto, nada funcionará. A configuração
PYTHONPATH permite controlar onde você coloca seus arquivos de origem Python, e a
configuração Use UDF Server mantém o interpretador Python em execução entre as
chamadas no Windows, o que pode melhorar muito o desempenho.
Python Interpreter
xlwings depende de uma instalação do Python instalada localmente. Isso, no entanto,
não significa necessariamente que o destinatário da sua ferramenta xlwings precise
mexer na configuração antes de poder usar a ferramenta. Como mencionado
anteriormente, você pode dizer a eles para instalar a distribuição do Anaconda com as
configurações padrão, que a instalarão no diretório inicial do usuário. Se você usar
variáveis de ambiente em sua configuração, xlwings encontrará o caminho correto
para o interpretador Python. Uma variável de ambiente é uma variável definida no
computador do usuário que permite que os programas consultem informações
específicas desse ambiente, como o nome da pasta pessoal do usuário atual. Como
exemplo, no Windows, defina o Conda Path como %USERPROFILE%\anaconda3 e
no macOS, defina Interpreter_Mac como $HOME/opt/anaconda3/bin/python.
Esses caminhos serão resolvidos dinamicamente para o caminho de instalação padrão
do Anaconda.
PYTHONPATH
Por padrão, o xlwings procura o arquivo de origem do Python no mesmo diretório que
o arquivo do Excel. Isso pode não ser ideal quando você fornece sua ferramenta para
usuários que não estão familiarizados com o Python, pois eles podem esquecer de
manter os dois arquivos juntos ao mover o arquivo do Excel. Em vez disso, você pode
colocar seus arquivos de origem do Python em uma pasta dedicada (isso pode estar em
uma unidade compartilhada) e adicionar essa pasta à configuração PYTHONPATH. Como
alternativa, você também pode colocar seus arquivos de origem em um caminho que já
faz parte do caminho de pesquisa do módulo Python. Uma maneira de conseguir isso
seria distribuir seu código-fonte como um pacote Python - instalá-lo o colocará no
diretório site-packages do Python, onde o Python encontrará seu código. Para obter
mais informações sobre como criar um pacote Python, consulte o Python Guia do
usuário.
RunPython: Use UDF Server (somente Windows)
Você deve ter notado que uma chamada RunPython pode ser bastante lenta. Isso
ocorre porque o xlwings inicia um interpretador Python, executa o código Python e,
finalmente, desliga o interpretador novamente. Isso pode não ser tão ruim durante o
desenvolvimento, pois garante que todos os módulos sejam carregados do zero toda vez
que você chamar o comando RunPython. Uma vez que seu código esteja estável,
porém, você pode querer ativar a caixa de seleção “RunPython: Use UDF Server” que
está disponível apenas no Windows. Isso usará o mesmo servidor Python usado pelas
funções definidas pelo usuário (o tópico do Capítulo 12) e manterá a sessão Python em
execução entre as chamadas, o que será muito mais rápido.
Página 219 Capítulo 10
Observe, no entanto, que você precisa clicar no botão Reiniciar servidor UDF na faixa
de opções após as alterações de código.
xlwings PRO
Embora este livro faça uso apenas da versão gratuita e de código aberto do xlwings, há
também um pacote comercial PRO disponível para financiar a manutenção e o
desenvolvimento contínuos do pacote de código aberto. Algumas das funcionalidades
adicionais que o xlwings PRO oferece são: O
Para mais detalhes sobre o xlwings PRO e para solicitar uma licença de teste, consulte o
xlwings página inicial.
Conclusão
Este capítulo começou mostrando como é fácil executar código Python a partir do Excel:
com o Anaconda instalado, você só precisa executar xlwings addin install seguido por
xlwings quickstart myproject, e você está pronto para clicar no botão Run main no
xlwings add -in ou use a função VBA RunPython. A segunda parte apresentou algumas
configurações que facilitam a implantação da ferramenta xlwings para seus usuários finais.
O fato de o xlwings vir pré-instalado com o Anaconda ajuda muito a diminuir as barreiras
de entrada para novos usuários.
Neste capítulo, estávamos apenas usando o exemplo Hello World para aprender como tudo
funciona. O próximo capítulo usa esses fundamentos para construir o Python Package
Tracker, um aplicativo de negócios completo.
Página 220
CAPÍTULO 11
O rastreador de pacotes Python
Neste capítulo, criaremos um aplicativo de negócios típico que baixa dados da Internet e os
armazena em um banco de dados antes de visualizá-los no Excel. Isso ajudará você a entender
qual o papel que o xlwings desempenha em tal aplicativo e permitirá que você veja como é
fácil conectar-se a sistemas externos com Python. Em uma tentativa de construir um projeto
próximo a um aplicativo do mundo real, mas relativamente simples de seguir, criei o Python
Package Tracker, uma ferramenta do Excel que mostra o número de lançamentos por ano
para um determinado pacote Python. Apesar de ser um estudo de caso, você pode realmente
achar a ferramenta útil para entender se um determinado pacote Python está sendo
desenvolvido ativamente ou não.
Depois de conhecer o aplicativo, passaremos por alguns tópicos que precisamos entender
para poder seguir seu código: veremos como podemos baixar dados da Internet e como
podemos interagir com bancos de dados antes de aprender sobre tratamento de exceções em
Python, um conceito importante quando se trata de desenvolvimento de aplicativos. Assim
que terminarmos essas preliminares, passaremos pelos componentes do Python Package
Tracker para ver como tudo se encaixa. Para encerrar este capítulo, veremos como funciona
a depuração do código xlwings. Como os dois últimos capítulos, este capítulo exige que
você tenha uma instalação do Microsoft Excel no Windows ou no macOS. Vamos começar
levando o Python Package Tracker para um test drive!
Se tudo funcionar de acordo com o planejado, você verá a mensagem “Added xlwings com
sucesso” à direita de onde você digitou o nome do pacote. Além disso, você verá um carimbo
de data/hora da última atualização na seção Atualizar banco de dados, bem como uma seção
de log atualizada, onde diz que baixou xlwings com êxito e o armazenou no banco de dados.
Vamos fazer isso mais uma vez e adicionar o pacote pandas para que tenhamos mais alguns
dados para brincar. Agora, mude para a planilha Rastreador e selecione xlwings no menu
suspenso na célula B5 antes de clicar em Mostrar histórico. Sua tela agora deve ser
semelhante à Figura 11-2, mostrando a versão mais recente do pacote, bem como um gráfico
com o número de versões ao longo dos anos.
Página 222 Capítulo 11
Agora você pode voltar para a planilha Database e adicionar pacotes adicionais. Sempre que
você quiser atualizar o banco de dados com as informações mais recentes do PyPI, clique no
botão Atualizar banco de dados: isso sincronizará seu banco de dados com os dados mais
recentes do PyPI.
Depois de dar uma olhada em como o Python Package Tracker funciona da perspectiva de
um usuário, vamos agora apresentar sua funcionalidade principal.
Página 223 Capítulo 11
Funcionalidade central
Nesta seção, apresentarei a funcionalidade central do Python Package Tracker: como buscar
dados por meio de APIs da Web e como consultar bancos de dados. Também mostrarei
como lidar com exceções, um tópico que inevitavelmente surgirá ao escrever o código do
aplicativo. Vamos começar com as APIs da Web!
APIs da Web
As APIs da Web são uma das formas mais populares de um aplicativo buscar dados da
Internet: API significa interface de programação de aplicativos e define como você
interage com um aplicativo programaticamente. Uma API da Web, portanto, é uma API
que é acessada por uma rede, geralmente a Internet. Para entender como as APIs da web
funcionam, vamos dar um passo atrás e ver o que acontece (em termos simplificados)
quando você abre uma página da web em seu navegador: depois de inserir uma URL na
barra de endereços, seu navegador envia uma solicitação GET ao servidor, pedindo a página
da web que você deseja. Uma solicitação GET é um método do protocolo HTTP que seu
navegador usa para se comunicar com o servidor. Quando o servidor recebe a solicitação,
ele responde enviando de volta o documento HTML solicitado, que seu navegador exibe:
voilà, sua página web foi carregada. O protocolo HTTP possui vários outros métodos; a
mais comum — além da solicitação GET — é a solicitação POST, que é usada para enviar
dados ao servidor (por exemplo, quando você preenche um formulário de contato em uma
página da web).
Embora faça sentido para os servidores enviar de volta uma página HTML bem formatada
para interagir com humanos, os aplicativos não se preocupam com o design e estão
interessados apenas nos dados. Portanto, uma solicitação GET para uma API da Web
funciona como a solicitação de uma página da Web, mas geralmente você obtém os dados
em JSON vez do formato HTML. JSON significa JavaScript Object Notation e é uma
estrutura de dados que é compreendida por praticamente todas as linguagens de
programação, o que o torna ideal para trocar dados entre diferentes sistemas. Embora a
notação esteja usando a sintaxe JavaScript, é muito próxima de como você usa dicionários e
listas (aninhados) em Python.diferenças são as seguintes:
API REST
Em vez de API web, muitas vezes você verá o termo REST ou RESTful API. REST significa
transferência de estado representacional e define uma API da Web que adere a certas
restrições. Em sua essência, a ideia do REST é que você acesse informações na forma de
recursos sem estado. Stateless significa que cada solicitação para uma API REST é
completamente independente de qualquer outra solicitação e precisa sempre fornecer o
conjunto completo de informações solicitadas. Observe que o termo API REST é muitas
vezes mal utilizado para significar qualquer tipo de API da Web, mesmo que não cumpra as
restrições REST.
O consumo de APIs da Web geralmente é muito simples (veremos como isso funciona com
o Python em breve) e quase todos os serviços oferecem uma. Se você deseja baixar sua playlist
favorita do Spotify, você deve emitir a seguinte solicitação GET (consulte o Spotify Web
Referência da API):
GET https://api.spotify.com/v1/playlists/playlist_id
Página 225 Capítulo 11
Ou, se você deseja obter informações sobre suas últimas viagens Uber, execute a seguinte
solicitação GET (consulte a API REST Uber):
GET https://api.uber.com/v1.2/history
Para usar essas APIs, no entanto, você precisa ser autenticado, o que geralmente significa
que você precisa de uma conta e um token que você pode enviar junto com suas solicitações.
Para o Python Package Tracker, precisaremos buscar dados do PyPI para obter informações
sobre os lançamentos de um pacote específico. Felizmente, a API da web do PyPI não requer
autenticação, então temos uma coisa a menos com que nos preocupar. Quando você der
uma olhada nos documentos da API JSON do PyPI, você verá que existem apenas dois
endpoints, ou seja, fragmentos de URL que são anexados ao URL base,
https://pypi.org/pypi:
GET /project_name/json
GET /project_name/version/json
O segundo endpoint fornece as mesmas informações que o primeiro, mas apenas para uma
versão específica. Para o Python Package Tracker, o primeiro endpoint é tudo o que
precisamos para obter os detalhes sobre as versões anteriores de um pacote, então vamos ver
como isso funciona. Em Python, uma maneira simples de interagir com uma API web é
usando o pacote Requests que vem pré-instalado com o Anaconda. Execute os seguintes
comandos para buscar dados PyPI sobre pandas:
In [5]: import requests
In [6]: response = requests.get("https://pypi.org/pypi/pandas/json")
response.status_code
Out[6]: 200
Cada resposta vem com um código de status HTTP: por exemplo, 200 significa OK e 404
significa Not Found. Você pode procurar a lista completa de códigos de status de resposta
HTTP nos documentos da Web da Mozilla. Você pode estar familiarizado com o código de
status 404 , pois seu navegador às vezes o exibe quando você clica em um link inativo ou
digita um endereço que não existe. Da mesma forma, você também receberá um código de
status 404 se executar uma solicitação GET com um nome de pacote que não existe no
PyPI. Para ver o conteúdo da resposta, é mais fácil chamar o método json do objeto
response, que transformará a string JSON da resposta em um dicionário Python:
In [7]: response.json()
A resposta é muito longa, então estou imprimindo um pequeno subconjunto aqui para
permitir que você entenda a estrutura:
Out[7]: {
'info': {
'bugtrack_url': None,
'license': 'BSD',
'maintainer': 'The PyData Development Team',
'maintainer_email': '[email protected]',
'name': 'pandas'
Página 226 Capítulo 11
},
'releases': {
'0.1': [
{
'filename': 'pandas-0.1.tar.gz',
'size': 238458,
'upload_time': '2009-12-25T23:58:31'
},
{
'filename': 'pandas-0.1.win32-py2.5.exe',
'size': 313639,
'upload_time': '2009-12-26T17:14:35'
}
]
}
}
Para obter uma lista com todos os lançamentos e suas datas, algo que precisamos para o
Python Package Tracker, podemos executar o seguinte código para percorrer o dicionário
releases:
In [8]: releases = []
for version, files in response.json()['releases'].items():
releases.append(f"{version}: {files[0]['upload_time']}")
releases[:3] # show the first 3 elements of the list
Out[8]: ['0.1: 2009-12-25T23:58:31',
'0.10.0: 2012-12-17T16:52:06',
'0.10.1: 2013-01-22T05:22:09']
Databases
Para poder usar os dados do PyPI mesmo quando não estiver conectado à internet, você
precisa armazená-los após o download. Embora você possa armazenar suas respostas JSON
como arquivos de texto em disco, uma solução muito mais confortável é usar um banco de
dados: isso permite consultar seus dados de maneira fácil. O Python Package Tracker está
usando SQLite, um banco de dados relacional. Sistemas de banco de dados relacionais
recebem seu nome de relação, que se refere à própria tabela do banco de dados (e não à
relação entre tabelas, que é um equívoco comum):
Página 227 Capítulo 11
seu objetivo maior é a integridade dos dados, que eles alcançam dividindo os dados em
diferentes tabelas (um processo chamado normalização) e aplicando restrições para evitar
dados inconsistentes e redundantes. Bancos de dados relacionais usam SQL (Structured
Query Language) para realizar consultas de banco de dados e estão entre os sistemas de
banco de dados relacionais baseados em servidor mais populares: SQL Server, Oracle,
PostgreSQL e MySQL. Como usuário do Excel, você também pode estar familiarizado com
o Microsoft Access base de dados.
Databases NoSQL
Atualmente, os bancos de dados relacionais têm forte concorrência dos bancos de dados
NoSQL que armazenam dados redundantes na tentativa de obter as seguintes vantagens:
Nenhuma junção de tabelas
Como os bancos de dados relacionais dividem seus dados em várias tabelas, muitas
vezes você precisa combinar as informações de duas ou mais tabelas juntando-as, o que
às vezes pode ser lento. Isso não é necessário com bancos de dados NoSQL, o que pode
resultar em melhor desempenho para determinados tipos de consultas.
Sem migrações de banco
Com sistemas de banco de dados relacionais, toda vez que você faz uma alteração na
estrutura da tabela, por exemplo, adicionando uma nova coluna a uma tabela, você
deve executar uma migração. Uma migração é um script que traz o banco de dados
para a nova estrutura desejada. Isso torna a implantação de novas versões de um
aplicativo mais complexa, resultando potencialmente em tempo de inatividade, algo
que é mais fácil de evitar com bancos de dados NoSQL.
Mais fácil de escalar
Databases NoSQL são mais fáceis de distribuir em vários servidores, pois não há tabelas
que são dependentes umas das outras. Isso significa que um aplicativo que usa um
banco de dados NoSQL pode escalar melhor quando sua base de usuários dispara.
Os bancos de dados NoSQL vêm em muitos sabores: alguns bancos de dados são simples
armazenamentos de valores-chave, ou seja, funcionam de maneira semelhante a um
dicionário em Python (por exemplo, Redis); outros permitem o armazenamento de
documentos, muitas vezes no formato JSON (por exemplo, MongoDB). Alguns bancos de
dados até combinam os mundos relacional e NoSQL: o PostgreSQL, que é um dos bancos
de dados mais populares na comunidade Python, é tradicionalmente um banco de dados
relacional, mas também permite armazenar dados no formato JSON — sem perder a
capacidade de consultá-los através do SQL.
SQLite, o banco de dados que vamos usar, é um banco de dados baseado em arquivos como
o Microsoft Access. No entanto, ao contrário do Microsoft Access, que funciona apenas no
Windows, o SQLite funciona em todas as plataformas suportadas pelo Python. Por outro
lado, o SQLite não permite que você construa uma interface de usuário como o Microsoft
Access, mas o Excel é útil para esta parte.
Página 228 Capítulo 11
Vamos agora dar uma olhada na estrutura do banco de dados do Package Tracker antes de
descobrir como podemos usar Python para conectar-se a bancos de dados e fazer consultas
SQL. Então, para concluir esta introdução sobre bancos de dados, veremos as injeções de
SQL, uma vulnerabilidade popular de aplicativos orientados a banco de dados.
2 pandas
Figura 11-3 é um diagrama de banco de dados que mostra as duas tabelas novamente
esquematicamente. Você pode ler os nomes das tabelas e colunas e obter informações sobre
as chaves primária e estrangeira:
Chave primária
Bancos de dados relacionais exigem que cada tabela tenha uma chave primária. Uma
chave primária é uma ou mais colunas que identificam exclusivamente uma linha (uma
linha também é chamada de registro). No caso da tabela packages, a chave primária
é package_id e no caso
Página 229 Capítulo 11
da tabela package_versions, a chave primária é a chamada chave composta, ou seja,
uma combinação de package_id e version_string.
Chave estrangeira
A coluna package_id na tabela package_versions é uma chave estrangeira para a
mesma coluna na tabela packages, simbolizada pela linha que conecta as tabelas: uma
chave estrangeira é uma restrição que, no nosso caso, garante que cada package_id da
tabela package_versions existe na tabela packages — isso garante a integridade
dos dados. As ramificações na extremidade direita da linha de conexão simbolizam a
natureza do relacionamento: um package pode ter muitos package_versions, que é
chamado de relacionamento um-para-muitos.
Figura 11-3. Diagrama do banco de dados (as chaves primárias estão em negrito)
Para dar uma olhada no conteúdo das tabelas do banco de dados e executar consultas SQL,
você pode instalar uma extensão do VS Code chamada SQLite (consulte os documentos de
extensão SQLite para mais detalhes) ou use um software de gerenciamento SQLite
dedicado, que há em abundância. Nós, no entanto, usaremos Python para executar
consultas SQL. Antes de mais nada, vamos ver como podemos nos conectar a um banco de
dados!
Conexões de banco
Para se conectar a um banco de dados a partir do Python, você precisa de um driver, ou seja,
um pacote Python que saiba se comunicar com o banco de dados que você está usando.
Cada banco de dados requer um driver diferente e cada driver usa uma sintaxe diferente,
mas felizmente existe um pacote poderoso chamado SQLAlchemy que abstrai a maioria das
diferenças entre os vários bancos de dados e drivers. SQLAlchemy é usado principalmente
como um mapeador relacional de objetos (ORM) que traduz seus registros de banco de
dados em objetos Python, um conceito que muitos desenvolvedores - embora não todos -
acham mais natural trabalhar com ele. Para manter as coisas simples, estamos ignorando a
funcionalidade ORM e usando apenas SQLAlchemy para facilitar a execução de consultas
SQL brutas. SQLAlchemy também é usado nos bastidores quando você usa pandas para ler
e gravar tabelas de banco de dados na forma de DataFrames. A execução de uma consulta
de banco de dados a partir de pandas envolve três níveis de pacotes — pandas, SQLAlchemy
e o driver de banco de dados — conforme mostrado na Figura 11-4. Você pode executar
consultas de banco de dados de cada um desses três níveis.
Página 230 Capítulo 11
A Tabela 11-3 mostra qual driver o SQLAlchemy usa por padrão (alguns bancos de dados
podem ser usados com mais de um driver). Ele também fornece o formato da string de
conexão do banco de dados — usaremos a string de conexão em um momento em que
executaremos consultas SQL reais.
Exceto para SQLite, você geralmente precisa de uma senha para se conectar a um banco de
dados. E como as strings de conexão são URLs, você terá que usar a versão codificada de
URL de suas senhas se houver algum caractere especial nelas. Aqui está como você pode
imprimir a versão codificada de URL de sua senha:
In [9]: import urllib.parse
In [10]: urllib.parse.quote_plus("pa$$word")
Out[10]: 'pa%24%24word'
Tendo apresentado pandas, SQLAlchemy e o driver de banco de dados como os três níveis a
partir dos quais podemos nos conectar a bancos de dados, vamos ver como eles se
comparam na prática fazendo algumas consultas SQL!
Página 231 Capítulo 11
Consultas SQL
Mesmo que você seja novo em SQL, não deverá ter problemas para entender as poucas
consultas SQL que usarei nos exemplos a seguir e no Python Package Tracker. SQL é uma
linguagem declarativa, o que significa que você diz ao banco de dados o que deseja em vez
do que fazer. Algumas consultas são quase como um inglês simples:
SELECT * FROM packages
Isso informa ao banco de dados que você deseja selecionar todas as colunas da tabela de
pacotes. No código de produção, você não gostaria de usar o curinga * que significa todas
as colunas mas sim especificar cada coluna explicitamente, pois isso torna sua consulta
menos propensa a erros:
SELECT package_id, package_name FROM packages
Agora que você sabe como executar consultas SQL simples, vamos encerrar esta seção
examinando as injeções de SQL, que podem representar um risco de segurança para seu
aplicativo.
Injeção de SQL
Se você não proteger suas consultas SQL corretamente, um usuário mal-intencionado pode
executar código SQL arbitrário injetando instruções SQL em campos de entrada de dados:
por exemplo, em vez de selecionar um nome de pacote como xlwings no menu suspenso do
Rastreador de Pacotes Python, eles podem enviar uma instrução SQL que altera sua
consulta pretendida. Isso pode expor informações confidenciais ou executar ações
destrutivas, como excluir uma tabela. Como você pode evitar isso? Vamos primeiro dar uma
olhada na seguinte consulta de banco de dados, que o Package Tracker executa quando você
seleciona xlwings e clica em Show History:1
SELECT v.uploaded_at, v.version_string
FROM packages p
INNER JOIN package_versions v ON p.package_id = v.package_id
WHERE p.package_id = 1
Essa consulta une as duas tabelas e retorna apenas as linhas em que package_id é 1. Para
ajudá-lo a entender essa consulta com base no que aprendemos no Capítulo 5, se packages
e package_versions fossem pandas DataFrames, você poderia escrever:
df = packages.merge(package_versions, how="inner", on="package_id")
df.loc[df["package_id"] == 1, ["uploaded_at", "version_string"]]
É óbvio que o package_id precisa ser uma variável onde agora temos um 1 para retornar
as linhas corretas dependendo do pacote que está selecionado. Conhecendo as f-strings do
Capítulo 3, você pode ficar tentado a alterar a última linha da consulta SQL assim:
f"WHERE p.package_id = {package_id}"
Embora isso tecnicamente funcione, você nunca deve fazer isso ao abrir até a porta para
injeção de SQL: por exemplo, alguém poderia enviar '1 OR TRUE' em vez de um inteiro
representando o package_id. A consulta resultante retornaria as linhas de toda a tabela em
vez de apenas aquelas em que package_id é 1.
Agrupar a consulta SQL com o text tem a vantagem de poder usar a mesma sintaxe para
espaços reservados em diferentes bancos de dados. Caso contrário, você teria que usar o
espaço reservado que o driver do banco de dados usa: sqlite3 usa ? e psycopg2 usa %s,
por exemplo.
Você pode argumentar que a injeção de SQL não é um grande problema quando seus
usuários têm acesso direto ao Python e podem executar código arbitrário no banco de dados
de qualquer maneira. Mas se você pegar seu protótipo xlwings e transformá-lo em um
aplicativo da web um dia, isso se tornará um grande problema, então é melhor fazê-lo
corretamente desde o início.
Além das APIs da Web e bancos de dados, há outro tópico sobre o qual saltamos até agora
e que é indispensável para o desenvolvimento sólido de aplicativos: tratamento de exceções.
Vamos ver como isso funciona!
Página 235 Capítulo 11
Exceções
Mencionei o tratamento de exceções no Capítulo 1 como um exemplo de onde o VBA com
seu mecanismo GoTo ficou para trás. Nesta seção, mostro como o Python usa o mecanismo
try/except para lidar com erros em seus programas. Sempre que algo está fora do seu
controle, erros podem e vão acontecer. Por exemplo, o servidor de e-mail pode estar inativo
quando você tenta enviar um e-mail ou pode estar faltando um arquivo que seu programa
espera — no caso do Python Package Tracker, esse pode ser o arquivo de banco de dados.
Lidar com a entrada do usuário é outra área em que você precisa se preparar para entradas
que não fazem sentido. Vamos praticar — se a função a seguir for chamada com um zero,
você obterá um ZeroDivisionError:
In [24]: def print_reciprocal(number):
result = 1 / number
print(f"The reciprocal is: {result}")
In [25]: print_reciprocal(0) # This will raise an error
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-25-095f19ebb9e9> in <module>
----> 1 print_reciprocal(0) # This will raise an error
<ipython-input-24-88fdfd8a4711> in print_reciprocal(number)
1 def print_reciprocal(number):
----> 2 result = 1 / number
3 print(f"The reciprocal is: {result}")
Para permitir que seu programa reaja normalmente a tais erros, use as instruções try/except
(isto é o equivalente a o exemplo de VBA no Capítulo 1):
In [26]: def print_reciprocal(number):
try:
result = 1 / number
except Exception as e:
# "as e" makes the Exception object available as variable "e"
# "repr" stands for "printable representation" of an object
# and gives you back a string with the error message
print(f"There was an error: {repr(e)}")
result = "N/A"
else:
print("There was no error!")
finally:
print(f"The reciprocal is: {result}")
Sempre que ocorrer um erro no bloco try, a execução do código passa para o bloco except
onde você pode lidar com o erro: isso permite que você dê feedback útil ao usuário ou grave
o erro em um arquivo de log. A cláusula else só é executado se não houver erro gerado
durante o bloco try e o bloco finally é executado sempre, se um erro foi gerado ou não.
Página 236 Capítulo 11
Muitas vezes, você vai se safar apenas com os blocos try e except. Vamos ver a saída da
função com entradas diferentes:
In [27]: print_reciprocal(10)
There was no error!
The reciprocal is: 0.1
In [28]: print_reciprocal("a")
There was an error: TypeError("unsupported operand type(s) for /: 'int'
and 'str'")
The reciprocal is: N/A
In [29]: print_reciprocal(0)
There was an error: ZeroDivisionError('division by zero')
The reciprocal is: N/AA
A forma como usei a instrução except significa que qualquer exceção que ocorrer no bloco
try causará execução de código para continuar no bloco, except. Normalmente, não é
isso que você quer. Você deseja verificar um erro o mais específico possível e lidar apenas
com aqueles que você espera. Caso contrário, seu programa pode falhar por algo
completamente inesperado, o que dificulta a depuração. Para corrigir isso, reescreva a
função da seguinte forma, verificando explicitamente os dois erros que esperamos (estou
deixando de lado as instruções else e finally):
In [30]: def print_reciprocal(number):
try:
result = 1 / number
print(f"The reciprocal is: {result}")
except (TypeError, ZeroDivisionError):
print("Please type in any number except 0.")
Vamos executar o código novamente:
In [31]: print_reciprocal("a")
Please type in any number except 0.
Se você quiser tratar um erro de forma diferente dependendo da exceção, trate-os
separadamente:
In [32]: def print_reciprocal(number):
try:
result = 1 / number
print(f"The reciprocal is: {result}")
except TypeError:
print("Please type in a number.")
except ZeroDivisionError:
print("The reciprocal of 0 is not defined.")
In [33]: print_reciprocal("a")
Please type in a number.
Página 237 Capítulo 11
In [34]: print_reciprocal(0)
The reciprocal of 0 is not defined.
Agora que você conhece o tratamento de erros, APIs da Web e bancos de dados, você está
pronto para passar para a próxima seção, onde passaremos por cada componente do Python
Package Tracker.
Estrutura do aplicativo
Nesta seção, veremos os bastidores do Python Package Tracker para entender como tudo
funciona. Primeiro, percorreremos o frontend do aplicativo, ou seja, o arquivo Excel, antes
de examinar seu backend, ou seja, o código Python. Para encerrar esta seção, veremos como
funciona a depuração de um projeto xlwings, uma habilidade útil com projetos do tamanho
e complexidade do Package Tracker.
No diretório packagetracker no repositório complementar, você encontrará quatro
arquivos. Você se lembra quando falei sobre separação de interesses no Capítulo 1? Agora
podemos mapear esses arquivos para as diferentes camadas, conforme mostrado na Tabela
11-4:
Nesse contexto, vale ressaltar que a camada de apresentação, ou seja, o arquivo Excel, não
contém uma fórmula de célula única, o que torna a ferramenta muito mais fácil de auditar
e controlar.
Model-View-Controller (MVC)
A separação de interesses tem muitas faces e a divisão mostrada na Tabela 11-4 é apenas uma
possibilidade. Outro padrão de design popular que você pode encontrar com relativa
rapidez é chamado de controlador de exibição de modelo (MVC). No mundo MVC, o
núcleo da aplicação é o modelo onde todos os dados e geralmente a maior parte da lógica de
negócios é tratada. Enquanto a visualização corresponde à camada de apresentação, o
controlador é apenas uma camada fina que fica entre o modelo e a visualização para garantir
que eles estejam sempre sincronizados. Para manter as coisas simples, não estou usando o
padrão MVC neste livro.
Página 238 Capítulo 11
Agora que você sabe pelo que cada arquivo é responsável, vamos seguir em frente e dar uma
olhada em como o frontend do Excel foi configurado!
Frontend
Ao construir um aplicativo da Web, você diferencia o frontend, que é a parte do aplicativo
que é executada no navegador, e o backend, que é o código que é executado no servidor.
Podemos aplicar a mesma terminologia com ferramentas xlwings: o frontend é o arquivo
Excel e o backend é o código Python que você chama via RunPython. Se você deseja
construir o frontend do zero, comece executando o seguinte comando em um Prompt do
Anaconda (certifique-se de cd primeiro no diretório de sua escolha):
(base)> xlwings quickstart packagetracker
Embora você possa assumir o texto e a formatação da Figura 11-5, preciso fornecer mais
alguns detalhes sobre as coisas que não são visíveis:
Botões
Para tornar a ferramenta um pouco menos parecida com Windows 3.1, não usei os
botões macro padrão que usamos no capítulo anterior. Em vez disso, fui em Inserir >
Formas e inseri um retângulo arredondado. Se você quiser usar o botão padrão, tudo
bem também, mas neste ponto, não atribua uma macro ainda.
Página 239 Capítulo 11
Intervalos nomeados
Para tornar a ferramenta um pouco mais fácil de manter, usaremos intervalos
nomeados em vez de endereços de células no código Python. Portanto, adicione os
intervalos nomeados conforme mostrado na Tabela 11-5.
Validação de dados
Usamos a validação de dados para fornecer a lista suspensa na célula B5 da planilha
Rastreador. Para adicioná-lo, selecione a célula B5, vá para Dados > Validação de Dados
e, em Permitir, selecione Lista. Em source, defina a seguinte fórmula:
=INDIRECT("dropdown_content[packages]")
Em seguida, confirme com OK. Esta é apenas uma referência ao corpo da tabela, mas
como o Excel não aceita uma referência de tabela diretamente, temos que envolvê-la em
uma fórmula INDIRECT, que resolve a tabela para seu endereço. Ainda assim, usando
uma tabela, ele redimensionará adequadamente o intervalo mostrado no menu
suspenso quando adicionarmos mais pacotes.
Formatação Condicional
Quando você adiciona um pacote, pode haver alguns erros que gostaríamos de mostrar
ao usuário: o campo pode estar vazio, o pacote pode já existir no banco de dados ou
pode estar faltando no PyPI. Para mostrar o erro em vermelho e outras mensagens em
preto, usaremos um truque simples baseado na formatação condicional: queremos
uma fonte vermelha sempre que a mensagem contiver a palavra “erro”. Na planilha
Banco de Dados, selecione a célula C5, onde escreveremos a mensagem. Em seguida, vá
para Home > Formatação Condicional > Realçar Regras de Células > Texto que
contém. Insira o valor error e selecione Red Text no menu suspenso como mostrado
na Figura 11-8e clique em OK. Aplique o mesmo formato condicional à célula C5 na
planilha Rastreador.
Sub ShowHistory()
RunPython "import packagetracker; packagetracker.show_history()"
End Sub
Sub UpdateDatabase()
RunPython "import packagetracker; packagetracker.update_database()"
End Sub
Em seguida, clique com o botão direito em cada botão, selecione Assign Macro e selecione
a macro que corresponde ao botão. Figura 11-9 mostra o botão Mostrar histórico, mas
funciona da mesma forma para os botões Adicionar pacote e Atualizar banco de dados.
Backend
O código dos dois arquivos Python packagetracker.py e database.py é muito longo para
ser mostrado aqui, então você precisará abri-los do repositório complementar no VS Code.
No entanto, vou me referir a alguns trechos de código nesta seção para explicar alguns
conceitos-chave. Vamos ver o que acontece quando você clica no botão Adicionar Pacote
na planilha Banco de Dados. O botão tem a seguinte macro VBA atribuída:
Sub AddPackage()
RunPython "import packagetracker; packagetracker.add_package()"
End Sub
Como você vê, a função RunPython chama a função Python add_package no módulo
packagetracker como mostrado no Exemplo 11-1.
def add_package():
db_sheet = xw.Book.caller().sheets["Database"]
package_name = db_sheet["new_package"].value
feedback_cell = db_sheet["new_package"].offset(column_offset=1)
feedback_cell.clear_contents()
if not package_name:
feedback_cell.value = "Error: Please provide a name!"
return
if requests.get(f"{BASE_URL}/{package_name}/json",
timeout=6).status_code != 200:
feedback_cell.value = "Error: Package not found!"
return
error = database.store_package(package_name)
db_sheet["new_package"].clear_contents()
if error:
feedback_cell.value = f"Error: {error}"
else:
feedback_cell.value = f"Added {package_name} successfully."
Página 243 Capítulo 11
update_database()
refresh_dropdown)
Por padrão, Requests está esperando para sempre por uma resposta que pode levar o
aplicativo a “travar” nos casos em que o PyPI tem um problema e está respondendo
lentamente. É por isso que para o código de produção, você deve sempre incluir um
parâmetro explícito timeout.
import sqlalchemy
import pandas as pd
...
# Database engine
engine = sqlalchemy.create_engine(f"sqlite:///{db_path}")
Página 244 Capítulo 11
Se você precisar de uma atualização do que esta linha faz, dê uma olhada no início do
Capítulo 7, onde eu explico no código do relatório de vendas.
Embora este trecho se preocupe em reunir o caminho do arquivo de banco de dados, ele
também mostra como contornar um erro comum ao trabalhar com qualquer tipo de
arquivo, seja uma imagem, um arquivo CSV ou, como neste caso , um arquivo de banco de
dados. Quando você monta um script Python rápido, pode usar apenas um caminho
relativo como fiz na maioria das amostras de notebook Jupyter:
engine = sqlalchemy.create_engine("sqlite:///packagetracker.db")
Isso funciona desde que seu arquivo esteja em seu diretório de trabalho. No entanto,
quando você executa esse código do Excel via RunPython, o diretório de trabalho pode ser
diferente, o que fará com que o Python procure o arquivo na pasta errada - você receberá
um erro File not found. Você pode resolver esse problema fornecendo um caminho
absoluto ou criando um caminho como fazemos no Exemplo 11-2. Isso garante que o
Python esteja procurando o arquivo no mesmo diretório que o arquivo de origem, mesmo
que você execute o código do Excel via RunPython.
Se você quiser criar o Python Package Tracker do zero, precisará criar o banco de dados
manualmente: execute o arquivo database.py como um script, por exemplo, clicando no
botão Run File no VS Code. Isso criará o arquivo de banco de dados packagetracker.db
com as duas tabelas. O código que cria o banco de dados é encontrado na parte inferior de
database.py:
if __name__ == " main ":
create_db()
Enquanto a última linha chama a função create_db, o significado da instrução anterior é
explicado na dica a seguir .
if name == “main ”
Você verá esta if na parte inferior de muitos arquivos Python. Ele garante que esse
código seja executado apenas quando você executa o arquivo como um script, por
exemplo, a partir de um Prompt do Anaconda executando python database.py
ou clicando no botão Executar arquivo no VS Code. Ele, no entanto, não será
acionado quando você executar o arquivo importando-o como um módulo, ou
seja, fazendo import database em seu código. A razão para isso é que o Python
atribui o nome main__ para o arquivo se você executá-lo diretamente como
script, enquanto ele será chamado pelo nome do módulo (database) quando você
o executar por meio da instrução import. Como o Python rastreia o nome do
arquivo em uma variável chamada __name, a instrução if será avaliada como True
somente quando você executá-la como script; ele não será acionado quando você o
importar do arquivo packagetracker.py.
Página 245 Capítulo 11
O resto do módulo database executa instruções SQL via SQLAlchemy e métodos dos
pandas to_sql e read_sql para que você tenha uma ideia de ambas as abordagens.
Depuração
Para depurar facilmente seus scripts xlwings, execute suas funções diretamente do VS Code,
em vez de executá-las clicando em um botão no Excel. As seguintes linhas na parte inferior
do arquivo packagetracker.py o ajudarão a depurar a função add_package (este é o mesmo
código que você também encontrará na parte inferior de um projeto quickstart):
if __name__ == " main ":
xw.Book("packagetracker.xlsm").set_mock_caller()
add_package())
Acabamos de ver como esta instrução if funciona quando estávamos olhando o código
database.py; veja a dica anterior.
2 No momento da redação deste artigo, essa opção ainda não estava disponível no macOS.
Página 247 Capítulo 11
Conclusão
Este capítulo mostrou que é possível construir aplicativos razoavelmente complexos com
um mínimo de esforço. Poder aproveitar pacotes Python poderosos como Requests ou
SQLAlchemy faz toda a diferença para mim quando comparo isso com o VBA, onde falar
com sistemas externos é muito mais difícil. Se você tiver casos de uso semelhantes, eu
recomendo que você analise mais de perto as solicitações e o SQL-Alchemy — ser capaz de
lidar eficientemente com fontes de dados externas permitirá que você faça copiar/colar uma
coisa do passado.
Em vez de clicar em botões, alguns usuários preferem criar suas ferramentas do Excel usando
fórmulas de células. O próximo capítulo mostra como o xlwings permite que você escreva
funções definidas pelo usuário em Python, permitindo que você reutilize a maioria dos
conceitos de xlwings que aprendemos até agora.
Página 248
CAPÍTULO 12
Funções definidas pelo usuário
(UDFs)
Os três capítulos anteriores mostraram como automatizar o Excel com um script Python e
como executar esse script do Excel com o clique de um botão. Este capítulo apresenta
funções definidas pelo usuário (UDFs) como outra opção para chamar código Python do
Excel com xlwings. UDFs são funções Python que você usa em células do Excel da mesma
forma que você usa funções internas como SUM ou AVERAGE. Como no capítulo anterior,
começaremos com o comando quickstart que nos permite experimentar uma primeira
UDF em pouco tempo. Em seguida, passamos para um estudo de caso sobre como buscar e
processar dados do Google Trends como uma desculpa para trabalhar com UDFs mais
complexas: aprenderemos como trabalhar com DataFrames e gráficos pandas, bem como
depurar UDFs. Para concluir este capítulo, vamos nos aprofundar em alguns tópicos
avançados com foco no desempenho. Infelizmente, o xlwings não suporta UDFs no
macOS, o que torna este capítulo o único capítulo que exige que você execute os exemplos
no Windows.1
1 A implementação do Windows usa um servidor COM (apresentei brevemente a tecnologia COM no Capítulo 9).
Como o COM não existe no macOS, as UDFs teriam que ser reimplementadas do zero, o que dá muito trabalho e
simplesmente ainda não foi feito.
Página 249 Capítulo 12
Iniciando com UDFs
Esta seção começa com os pré-requisitos para escrever UDFs antes que possamos usar o
comando quickstart para executar nossa primeira UDF. Para acompanhar os exemplos
deste capítulo, você precisará do suplemento xlwings instalado e ter a opção do Excel
“Confiar no acesso ao modo de objeto do projeto VBA” habilitada:
Suplemento
Presumo que você tenha o suplemento xlwings instalado conforme explicado no
Capítulo 10. No entanto, esse não é um requisito difícil: embora facilite o
desenvolvimento, especialmente para clicar no botão Import Functions, ele não é
necessário para implantação e pode ser substituído configurando a pasta de trabalho no
modo autônomo — para obter detalhes, consulte o Capítulo 10.
Acesso confiável ao modelo de objeto do projeto VBA
Para poder escrever seus primeiros UDFs, você precisará alterar uma configuração no
Excel: vá para Arquivo > Opções > Central de Confiabilidade > Configurações da
Central de Confiabilidade > Configurações de Macro e ative a caixa de seleção "Confiar
no acesso para o modelo de objeto do projeto VBA”, como na Figura 12-1. Isso permite
que o xlwings insira automaticamente um módulo VBA em sua pasta de trabalho
quando você clica no botão Import Functions no suplemento, como veremos em
breve. Como você só depende dessa configuração durante o processo de importação,
deve vê-la como uma configuração de desenvolvedor com a qual os usuários finais não
precisam se preocupar.
Com esses dois pré-requisitos, você está pronto para executar sua primeira UDF!
UDF Quickstart
Como de costume, a maneira mais fácil de decolar é usar o comando quickstart. Antes
de executar o seguinte em um prompt do Anaconda, certifique-se de mudar para o diretório
de sua escolha por meio do comando cd.
Página 250 Capítulo 12
Por exemplo, se você estiver em seu diretório pessoal e quiser mudar para a área de trabalho,
execute primeiro cd Desktop:
(base)> xlwings quickstart first_udf
Navegue até a pasta first_udf no File Explorer e abra first_udf.xlsm no Excel e first_udf.py
no Código VS. Em seguida, no add-in da faixa de opções xlwings, clique no botão Import
Functions. Por padrão, esta é uma ação silenciosa, ou seja, você só verá algo em caso de erro.
No entanto, se você ativar a caixa de seleção Mostrar console no suplemento do Excel e clicar
no botão Importar funções novamente, um prompt de comando será aberto e imprimirá o
seguinte:
xlwings server running [...]
Imported functions from the following modules: first_udf
O primeiro line imprime mais alguns detalhes que podemos ignorar - a parte importante é
que uma vez que esta linha é impressa, o Python está funcionando. A segunda linha
confirma que importou corretamente as funções do módulo first_udf. Agora digite
=hello("xlwings") na célula A1 da planilha ativa em first_udf.xlsm e depois de
pressionar Enter, você verá a fórmula avaliada conforme mostrado na Figura 12-2.
Vamos detalhar isso para ver como tudo funciona: comece examinando a função hello em
first_udf.py (Exemplo 12-1), qual é a parte do código quickstart que ignoramos até
agora.
import xlwings as xw
@xw.func
def hello(name):
return f"Hello {name}!"
Cada função que você marcar com @xw.func será importada para o Excel quando você
clicar em Import Functions no add-in xlwings. A importação de uma função a torna
disponível no Excel para que você possa usá-la em suas fórmulas de célula — entraremos em
detalhes técnicos em breve. @xw.func é um decorador, o que significa que você deve
colocá-lo diretamente no topo da definição da função.
Página 251 Capítulo 12
Se você quiser saber um pouco mais sobre como os decoradores trabalham, dê uma olhada
na barra lateral.
Decoradores de Função
Um decorador é um nome de função que você coloca no topo de uma definição de função,
começando com o sinal @. É uma maneira simples de alterar o comportamento de uma
função e é usada pelo xlwings para reconhecer quais funções você deseja disponibilizar no
Excel. Para ajudá-lo a entender como um decorador funciona, o exemplo a seguir mostra a
definição de um decorador chamado verbose que imprimirá algum texto antes e depois da
execução da função print_hello é executado. Tecnicamente, o decorador pega a função
(print_hello) e a fornece como argumento func para a função verbose. A função
interna chamada wrapper pode fazer o que for necessário; neste caso, imprime um valor
antes e depois de chamar a função print_hello. O nome da função interna não importa:
In [1]: # This is the definition of the function decorator
def verbose(func):
def wrapper():
print("Before calling the function.")
func()
print("After calling the function.")
return wrapper
In [2]: # Using a function decorator
@verbose
def print_hello():
print("hello!")
In [3]: # Effect of calling the decorated function
print_hello()
Before calling the function.
hello!
After calling the function.
No final deste capítulo, você encontrará Tabela 12-1 com um resumo de todos os
decoradores que o xlwings oferece.
Quando você clica no botão Import Functions no suplemento, o xlwings insere um módulo
VBA chamado xlwings_udfs na pasta de trabalho do Excel. Ele contém uma função VBA
para cada função Python que você importa: essas funções VBA wrapper cuidam da
execução da respectiva função em Python. Embora ninguém o impeça de olhar para o
módulo VBA xlwings_udfs abrindo o editor VBA com Alt+F11, você pode ignorá-lo,
pois o código é gerado automaticamente e quaisquer alterações serão perdidas quando você
clicar no botão Import Functions novamente. Vamos agora brincar com nossa função
hello em first_udf.py e substituir Hello no valor de retorno por Bye:
@xw.func
def hello(name):
return f"Bye {name}!"
Para recalcular a função no Excel, clique duas vezes na célula A1 para editar a fórmula (ou
selecione a célula e pressione F2 para ativar o modo de edição) e pressione Enter. Como
alternativa, digite o atalho de teclado Ctrl+Alt+F9: isso forçará o recálculo de todas as
planilhas em todas as pastas de trabalho abertas, incluindo a fórmula hello. Observe que
F9 (recalcular todas as planilhas em todas as pastas de trabalho abertas) ou Shift+F9
(recalcular a planilha ativa) não recalculará a UDF, pois o Excel acionará apenas um
recálculo de UDFs se uma célula dependente for alterada. Para alterar esse comportamento,
você pode tornar a função volátil adicionando o respectivo argumento ao decorador func:
@xw.func(volatile=True)
def hello(name):
return f"Bye {name}!"
As funções voláteis são avaliadas toda vez que o Excel executa um recálculo — se as
dependências da função foram alteradas ou não. Algumas das funções internas do Excel são
voláteis como =RAND() ou = NOW() e usar muitos deles tornará sua pasta de trabalho mais
lenta, então não exagere. Quando você altera o nome ou os argumentos de uma função ou
o decorador func como acabamos de fazer, você precisará reimportar sua função clicando
no botão Import Functions novamente: isso reiniciará o interpretador Python antes de
importar a função atualizada. Quando você agora altera a função de volta de Bye para
Hello, basta usar os atalhos de teclado Shift+F9 ou F9 para fazer com que a fórmula seja
recalculada, pois a função agora é volátil.
Página 253 Capítulo 12
Salve o arquivo Python depois de alterá-lo
Uma pegadinha comum é esquecer de salvar o arquivo de origem do Python depois
de fazer alterações. Portanto, sempre verifique se o arquivo Python foi salvo antes
de clicar no botão Import Functions ou recalcular as UDFs no Excel.
Por padrão, o xlwings importa funções de um arquivo Python no mesmo diretório com o
mesmo nome do arquivo Excel. Renomear e mover seu arquivo de origem do Python requer
alterações semelhantes às do Capítulo 10, quando estávamos fazendo o mesmo com as
chamadas RunPython: vá em frente e renomeie o arquivo de first_udf.py para hello.py.
Para informar o xlwings sobre essa alteração, adicione o nome do módulo, ou seja, hello
(sem a extensão .py!) para UDF Modules no suplemento xlwings, conforme mostrado na
Figura 12-3.
Agora vá em frente e mova hello.py para sua área de trabalho: isso requer que você adicione
o caminho da sua área de trabalho ao PYTHONPATH no suplemento xlwings. Como visto no
Capítulo 10, você pode usar variáveis de ambiente para conseguir isso, ou seja, você pode
definir a configuração PYTHONPATH no suplemento para %USERPROFILE%\Desktop. Se
você ainda tiver o caminho para a pasta pyscripts do Capítulo 10, sobrescreva-o ou deixe-o
lá, separando os caminhos com um ponto e vírgula. Após essas alterações, clique novamente
no botão Importar Funções e recalcule a função no Excel para verificar se tudo ainda
funciona.
Página 254 Capítulo 12
Configuração e implantação
Neste capítulo, estou sempre me referindo à alteração de uma configuração no
suplemento; no entanto, tudo do Capítulo 10 com relação à configuração e
implantação também pode ser aplicado a este capítulo. Isso significa que uma
configuração também pode ser alterada na planilha xlwings.conf ou uma
configuração de arquivo localizada no mesmo diretório que o arquivo Excel. E em
vez de usar o suplemento xlwings, você pode usar uma pasta de trabalho que foi
configurada no modo autônomo. Com UDFs, também faz sentido criar seu
próprio suplemento personalizado — isso permite que você compartilhe suas
UDFs entre todas as pastas de trabalho sem precisar importá-las para cada pasta de
trabalho. Para obter mais informações sobre como criar seu próprio suplemento
personalizado, consulte os documentos xlwings.
Amostras aleatórias
Os números do Google Trends são baseados em amostras aleatórias, o que significa
que você pode ver uma imagem ligeiramente diferente da Figura 12-4 , mesmo se
usar o mesmo local, período de tempo e termos de pesquisa da captura de tela.
Aperto o botão de download que você vê na Figura 12-4 para obter um arquivo CSV de
onde copiei os dados na pasta de trabalho do Excel de um projeto quickstart. Na próxima
seção, mostrarei onde encontrar esta pasta de trabalho - vamos usá-la para analisar os dados
com uma UDF diretamente do Excel!
import xlwings as xw
import pandas as pd
@xw.func
@xw.arg("df", pd.DataFrame, index=True, header=True)
def describe(df):
return df.describe()
arg é a abreviação de argument e permite que você aplique os mesmos conversores e opções
que eu estava usando no Capítulo 9 quando estava introduzindo a sintaxe xlwings. Em
outras palavras, o decorador oferece a mesma funcionalidade para UDFs que o método
options para objetos xlwings range. Formalmente, esta é a sintaxe do decorador arg:
@xw.arg("argument_name", convert=None, option1=value1, option2=value2, ...)
data_range = xw.Book("describe.xlsm").sheets[0]["A3:F263"]
df = data_range.options(pd.DataFrame, index=True, header=True).value
df.describe()
As opções index e header não seriam necessários, pois estão usando os argumentos
padrão, mas os incluí para mostrar como eles são aplicados com UDFs. Com describe.xlsm
como sua pasta de trabalho ativa, clique no botão Import Functions e digite
=describe(A3:F263) em uma célula livre, em H3, por exemplo. O que acontece quando
você pressiona Enter depende da sua versão do Excel — mais especificamente se sua versão
do Excel for recente o suficiente para oferecer suporte a matrizes dinâmicas. Se isso
acontecer, você verá a situação mostrada na Figura 12-5, ou seja, a saída da função describe
nas células H3:M11 é cercada por uma fina borda azul. Você só poderá ver a borda azul se
o cursor estiver dentro da matriz, e é tão sutil que você pode ter problemas para vê-la
claramente se observar a captura de tela em uma versão impressa do livro. Veremos como os
arrays dinâmicos se comportam em breve e você também pode aprender mais sobre eles na
barra lateral “Dynamic Arrays” na página 263.
Página 257 Capítulo 12
Se, no entanto, você estiver usando uma versão do Excel que não suporta matrizes
dinâmicas, parecerá que nada está acontecendo: por padrão, a fórmula retornará apenas a
célula superior esquerda em H3, que está vazio. Para corrigir isso, use o que a Microsoft
atualmente chama matrizes CSE. As matrizes CSE precisam ser confirmadas digitando a
combinação de teclas Ctrl+Shift+Enter em vez de pressionar apenas Enter - daí seu nome.
Vamos ver como eles funcionam em detalhes:
Agora você deve ver quase a mesma imagem da Figura 12-5 com estas diferenças:
Vamos agora tornar nossa função um pouco mais útil introduzindo um parâmetro opcional
chamado selection que nos permitirá especificar quais colunas queremos incluir em
nossa saída. Se você tiver muitas colunas e quiser incluir apenas um subconjunto na função
describe, isso pode se tornar um recurso útil. Altere a função da seguinte forma:
@xw.func
@xw.arg("df", pd.DataFrame)
def describe(df, selection=None):
Página 258 Capítulo 12
if selection is not None:
return df.loc[:, selection].describe()
else:
return df.describe()
Deixei de fora os argumentos index e header, pois eles estão usando os padrões, mas
sinta-se à vontade para deixá-los.
Figura 12-6. Matrizes dinâmicas (superior) versus matrizes CSE (inferior) após excluir
uma coluna
Após adicionar o decorador de retorno, salve o arquivo de origem do Python, alterne para
o Excel e pressione Ctrl+Alt+F9 para recalcular: isso redimensionará a matriz CSE e remova
a coluna #N/A. Como essa é uma solução alternativa, recomendo que você faça o que estiver
ao seu alcance para obter uma versão do Excel que ofereça suporte a matrizes dinâmicas.
Você geralmente não precisa fornecer um argumento explícito convert como xlwings
reconhece o tipo do valor de retorno automaticamente — esse é o mesmo comportamento
que vimos no Capítulo 9 com o método options ao gravar valores no Excel.
Página 260 Capítulo 12
Como exemplo, se você deseja suprimir o índice do DataFrame que você retorna, use este
decorador:
@xw.ret(index=False)
Dynamic Arrays
Tendo visto como os arrays dinâmicos funcionam no contexto da função describe, tenho
certeza você concordaria que eles são uma das adições mais fundamentais e empolgantes ao
Excel que a Microsoft criou em muito tempo. Eles foram apresentados oficialmente em
2020 para assinantes do Microsoft 365 que estão usando a versão mais recente do Excel. Para
ver se sua versão é recente o suficiente, verifique a existência da nova função UNIQUE:
comece a digitar =UNIQUE em uma célula e se o Excel sugerir o nome da função, matrizes
dinâmicas são suportadas. Se você usa o Excel com uma licença permanente em vez de como
parte da assinatura do Microsoft 365, é provável que o obtenha com a versão anunciada para
lançamento em 2021 e que provavelmente será chamada de Office 2021. Aqui estão algumas
notas técnicas sobre o comportamento dos arrays dinâmicos:
• Se os arrays dinâmicos sobrescreverem uma célula com um valor, você receberá um erro
#SPILL!. Depois de abrir espaço para o array dinâmico excluindo ou movendo a célula
que está no caminho, o array será escrito. Observe que o decorador de retorno xlwings
com expand="table" é menos inteligente e substituirá os valores das células existentes
sem aviso!
• Você pode fazer referência ao intervalo de uma matriz dinâmica usando a célula
superior esquerda seguida por um sinal #. Por exemplo, se sua matriz dinâmica estiver
no intervalo A1:B2 e você quiser somar todas as células, escreva =SUM(A1#).
• Se você quiser que suas matrizes se comportem como as matrizes herdadas CSE
novamente, inicie sua fórmula com um sinal @, por exemplo, para que uma
multiplicação de matrizes retorne uma matriz CSE herdada, use =@MMULT().
Para replicar o caso exato da versão online do Google Trends conforme mostrado na Figura
12-4, precisaremos encontrar os identificadores corretos para os termos de pesquisa com o
contexto “Linguagem de programação”. Para fazer isso, o pytrends pode imprimir os
diferentes contextos ou tipos que o Google Trends sugere no menu suspenso. No exemplo
de código a seguir, mid significa Machine ID, que é o ID que estamos procurando:
In [4]: from pytrends.request import TrendReq
In [5]: # First, let's instantiate a TrendRequest object
trend = TrendReq()
In [6]: # Now we can print the suggestions as they would appear
# online in the dropdown of Google Trends after typing in "Python"
trend.suggestions("Python")
Out[6]: [{'mid': '/m/05z1_', 'title': 'Python', 'type': 'Programming language'},
{'mid': '/m/05tb5', 'title': 'Python family', 'type': 'Snake'},
{'mid': '/m/0cv6_m', 'title': 'Pythons', 'type': 'Snake'},
{'mid': '/m/06bxxb', 'title': 'CPython', 'type': 'Topic'},
{'mid': '/g/1q6j3gsvm', 'title': 'python', 'type': 'Topic'}]
Repetir isso para as outras linguagens de programação nos permite recuperar o mid para
todas elas, e podemos escrever a UDF como mostrado no Exemplo 12-3. Você encontrará o
código-fonte no diretório google_trends na pasta udfs do repositório complementar.
import pandas as pd
from pytrends.request import TrendReq
import xlwings as xw
@xw.func(call_in_wizard=False)
@xw.arg("mids", doc="Machine IDs: A range of max 5 cells")
@xw.arg("start_date", doc="A date-formatted cell")
@xw.arg("end_date", doc="A date-formatted cell")
Página 262 Capítulo 12
def get_interest_over_time(mids, start_date, end_date):
"""Query Google Trends - replaces the Machine ID (mid) of
common programming languages with their human-readable
equivalent in the return value, e.g., instead of "/m/05z1_"
it returns "Python".
"""
# Check and transform parameters
assert len(mids) <= 5, "Too many mids (max: 5)"
start_date = start_date.date().isoformat()
end_date = end_date.date().isoformat()
Por padrão, o Excel chama a função quando você a abre no Assistente de Função.
Como isso pode torná-lo lento, especialmente com solicitações de API envolvidas,
estamos desativando isso.
A instrução assert é uma maneira fácil de gerar um erro caso o usuário forneça muitos
mids. O Google Trends permite um máximo de cinco mids por consulta.
pytrends espera as datas de início e término como uma única string no formato YYYY-
MM-DD YYYY-MM-DD. Como estamos fornecendo as datas de início e término como
células formatadas por data, elas chegarão como objetos datetime. Chamar os
métodos date e isoformat neles irá formatá-los corretamente.
A última coluna é chamada isPartial. True indica que o intervalo atual, por
exemplo, uma semana, ainda está em andamento e, portanto, ainda não possui todos
os dados. Para manter as coisas simples e estar de acordo com a versão online, estamos
descartando esta coluna ao retornar o DataFrame.
Agora abra google_trends.xlsm no repositório complementar, clique em Import
Functions no suplemento xlwings e chame a função get_interest_over_time da célula
A4, conforme mostrado na Figura 12-7.
Para obter ajuda com relação aos argumentos da função, clique no botão Inserir Função à
esquerda da barra de fórmulas enquanto a célula A4 estiver selecionada: isso abrirá o
Assistente de Função onde você encontrará suas UDFs na categoria xlwings. Após
selecionar get_interest_over_time, você verá o nome dos argumentos da função, bem
como a docstring como descrição da função (restrito aos primeiros 256 caracteres): veja a
Figura 12-8. Como alternativa, comece a digitar =get_interest_over_time( na célula
A4 (incluindo o parêntese de abertura) antes de pressionar o botão Inserir Função — isso o
levará diretamente à visualização mostrada na Figura 12-8. Observe que as UDFs retornam
as datas não formatadas. Para corrigir isso,sua escolha na categoria Data.
Página 264 Capítulo 12
Se você observar atentamente a Figura 12-7, poderá perceber pela borda azul ao redor do
array de resultados que estou usando arrays dinâmicos novamente. Como a captura de tela
é cortada na parte inferior e a matriz começa bem à esquerda, você vê apenas as bordas
superior e direita começando na célula A4, e até mesmo elas podem ser difíceis de reconhecer
na captura de tela. Se a sua versão do Excel não for compatível com matrizes dinâmicas, use
a solução alternativa adicionando o seguinte decorador de retorno à função
get_interest_over_time (abaixo dos decoradores existentes):
@xw.ret(expand="table")
Now que você sabe h omo trabalhar com UDFs mais complicadas, vamos ver como
podemos usar plotagens com UDFs!
import xlwings as xw
import pandas as pd
import matplotlib.pyplot as plt
@xw.func
@xw.arg("df", pd.DataFrame)
def plot(df, name, caller):
plt.style.use("seaborn")
if not df.empty:
caller.sheet.pictures.add(df.plot().get_figure(),
top=caller.offset(row_offset=1).top,
left=caller.left,
name=name, update=True)
return f"<Plot: {name}>"
Chame apenas o método plot se o DataFrame não estiver vazio. Chamar o método
plot em um DataFrame vazio geraria um erro.
Os argumentos top e left são usados apenas quando você insere o gráfico pela
primeira vez. Os argumentos fornecidos colocarão o gráfico em um local conveniente
— uma célula abaixo daquela de onde você chama essa função.
Embora você não precise retornar nada estritamente, torna sua vida muito mais fácil se
você retornar uma string: isso permite que você reconheça onde em sua planilha está
sua função de plotagem.
Em google_trends.xlsm, na célula H3, chame a função plot assim:
=plot(A4:F263, "History")
Se sua versão do Excel oferecer suporte a matrizes dinâmicas, use A4# em vez de A4:F263
para tornar a origem dinâmica como mostrado na Figura 12-9.
Vamos supor que você esteja um pouco confuso sobre como a função
get_interest_over_time funciona. Uma opção para entender melhor é depurar o
código — a próxima seção mostra como isso funciona com UDFs!
Depurando UDFs
Uma maneira simples de depurar uma UDF é usar a função print. Se você tiver a
configuração Show Console habilitada no add-in xlwings, poderá imprimir o valor de uma
variável no prompt de comando que aparece quando você chama seu UDF. Uma opção um
pouco mais confortável é usar o depurador do VS Code, que permitirá pausar nos pontos
de interrupção e percorrer o código linha por linha. Para usar o depurador do VS Code (ou
o depurador de qualquer outro IDE), você precisará fazer duas coisas:
Página 267 Capítulo 12
1. No suplemento do Excel, ative a caixa de seleção Depurar UDFs. Isso impede que o
Excel inicie automaticamente o Python, o que significa que você precisa fazer isso
manualmente, conforme explicado no próximo ponto.
2. A maneira mais fácil de executar o servidor Python UDF manualmente é adicionar as
seguintes linhas na parte inferior do arquivo que você está tentando depurar. Eu já
adicionei essas linhas na parte inferior do arquivo google_trends.py no repositório
complementar:
if __name__ == " main ":
xw.serve()
Como você deve se lembrar do Capítulo 11, esta declaração if garante que o código
seja executado apenas quando você executa o arquivo como um script — ele não é
executado quando você importa o código como um módulo. Com o comando serve
adicionado, execute google_trends.py no VS Code no modo de depuração
pressionando F5 e selecionando “Python File” — certifique-se de não executar o
arquivo clicando no botão Run File, pois isso ignoraria os pontos de interrupção.
Se você mantiver seu programa pausado por mais de noventa segundos em um ponto de
interrupção, o Excel mostrará um pop-up dizendo que “O Microsoft Excel está aguardando
outro aplicativo concluir uma ação OLE.” Isso não deve afetar sua experiência de depuração
além de ter que confirmar o pop-up para fazê-lo desaparecer assim que você terminar a
depuração. Para concluir esta sessão de depuração, clique no botão Parar no VS Code
(consulte Figura 12-10) e certifique-se de desmarcar a configuração Debug UDFs
novamente no suplemento da faixa de opções xlwings. Se você esquecer de desmarcar a
configuração Debug UDFs, suas funções retornarão um erro na próxima vez que você as
recalcular.
Esta seção mostrou a funcionalidade UDF mais comumente usada trabalhando com o
estudo de caso do Google Trends. A próxima seção abordará alguns tópicos avançados,
incluindo desempenho de UDF e o decorador xw.sub.
import numpy as np
import xlwings as xw
@xw.func
def revenue(base_fee, users, price):
return base_fee + users * price
@xw.func
@xw.arg("users", np.array, ndim=2)
@xw.arg("price", np.array)
def revenue2(base_fee, users, price):
return base_fee + users * price
Ao alterar a taxa base na célula B5 ou H5, respectivamente, você verá que o exemplo da
direita será muito mais rápido que o da esquerda. A diferença nas funções Python são
mínimas e diferem apenas nos decoradores de argumentos: a versão baseada em array lê em
users e prices como array NumPy — a única ressalva aqui é ler users como um vetor
de coluna bidimensional definindo ndim= 2 no decorador de argumentos. Você
provavelmente se lembra que os arrays NumPy são semelhantes aos DataFrames, mas sem
índice ou cabeçalho e com apenas um tipo de dados, mas se você quiser uma atualização
mais detalhada, dê outra olhada no Capítulo 4.
Cache
Quando você chama uma função deterministic, ou seja, uma função que dada as mesmas
entradas, sempre retorna a mesma saída, você pode armazenar o resultado em um cache:
chamadas repetidas da função não precisam mais esperar pelo cálculo lento, mas pode pegar
o resultado do cache onde já está pré-calculado. Isso é melhor explicado com um pequeno
exemplo. Um mecanismo de cache muito básico pode ser programado com um dicionário:
In [7]: import time
In [8]: cache = {}
Quando você chama esta função pela primeira vez, o cache está vazio. O código, portanto,
executará a cláusula else com a pausa artificial de dois segundos que imita um cálculo
lento. Após realizar o cálculo, ele adicionará o resultado ao dicionário cache antes de
retornar o resultado. Quando você agora chama essa função uma segunda vez com os
mesmos argumentos e durante a mesma sessão do Python, ela a encontrará no cache e a
retornará imediatamente, sem precisar realizar o cálculo lento novamente. O
armazenamento em cache de um resultado com base em seus argumentos também é
chamado de memoização. Assim, você verá a diferença de tempo quando chamar a função
pela primeira e segunda vez:
In [9]: %%time
slow_sum(1, 2)
Wall time: 2.01 s
Out[9]: 3
Página 272 Capítulo 12
In [10]: %%time
slow_sum(1, 2)
Wall time: 0 ns
Out[10]: 3
Python tem um decorador embutido chamado lru_cache que pode facilitar sua vida e que
você importa do módulo functools que faz parte do padrão biblioteca. lru significa menos
usado recentemente e significa que ele contém um número máximo de resultados (por
padrão 128) antes de se livrar dos mais antigos. Podemos usar isso com nosso exemplo do
Google Trends da última seção. Contanto que estejamos apenas consultando valores
históricos, podemos armazenar em cache o resultado com segurança. Isso não apenas tornará
várias chamadas mais rápidas, mas também diminuirá a quantidade de solicitações que
enviamos ao Google, diminuindo a chance de o Google nos bloquear – algo que pode
acontecer se você enviar muitas solicitações em pouco tempo.
Aqui estão as primeiras linhas da função get_interest_over_time com as alterações
necessárias para aplicar o cache:
from functools import lru_cache
import pandas as pd
from pytrends.request import TrendReq
import matplotlib.pyplot as plt
import xlwings as xw
@lru_cache
@xw.func(call_in_wizard=False)
@xw.arg("mids", xw.Range, doc="Machine IDs: A range of max 5 cells")
@xw.arg("start_date", doc="A date-formatted cell")
@xw.arg("end_date", doc="A date-formatted cell")
def get_interest_over_time(mids, start_date, end_date):
"""Query Google Trends - replaces the Machine ID (mid) of
common programming languages with their human-readable
equivalent in the return value, e.g., instead of "/m/05z1_"
it returns "Python".
"""
mids = mids.value
Por padrão, mids é uma lista. Isso cria um problema neste caso, pois funções com listas
como argumentos não podem ser armazenadas em cache. O problema subjacente é que
as listas são objetos mutáveis que não podem ser usados como chaves em dicionários;
consulte Apêndice C para obter mais informações sobre objetos mutáveis vs. imutáveis.
Página 273 Capítulo 12
Usando o conversor xw.Range nos permite recuperar mids como objeto xlwings
range ao invés de list, o que resolve nosso problema.
Para fazer o resto do código funcionar novamente, agora precisamos obter os valores
através da propriedade value do objeto xlwings range.
O conversor xw.Range também pode ser útil em outras circunstâncias, por exemplo, se
você precisar acessar as fórmulas das células em vez dos valores em sua UDF. No exemplo
anterior, você poderia escrever mids.formula para acessar as fórmulas das células. Você
encontrará o exemplo completo na pasta google_trends_cache dentro do diretório udfs do
repositório complementar.
Agora que você sabe como ajustar o desempenho de UDFs, vamos encerrar esta seção
apresentando o decorador xw.sub.
O Sub Decorador
No Capítulo 10, mostrei como acelerar a RunPython ativando a configuração Use UDF
Server. Se você vive em um mundo somente Windows, existe uma alternativa para a
combinação RunPython/Use UDF Server na forma do decorador xw.sub. Isso permitirá
que você importe suas funções Python como procedimentos Sub para o Excel, sem precisar
escrever manualmente nenhuma RunPython . No Excel, você precisará de um
procedimento Sub para poder anexá-lo a um botão — uma função do Excel, como você
obtém ao usar o decorador xw.func, não funcionará. Para experimentar isso, crie um novo
projeto quickstart chamado importsub. Como de costume, certifique-se de cd
primeiro no diretório onde você deseja que o projeto seja criado:
(base)> xlwings quickstart importsub
@xw.sub
def main():
wb = xw.Book.caller()
sheet = wb.sheets[0]
if sheet["A1"].value == "Hello xlwings!":
sheet["A1"].value = "Bye xlwings!"
else:
sheet["A1"].value = "Hello xlwings!"
No add-in xlwings, clique em Import Functions antes de pressionar Alt+F8 para ver as
macros disponíveis: além do SampleCall que usa RunPython, agora você também verá uma
macro chamada main. Selecione-o e clique no botão Executar - você verá a saudação familiar
na célula A1. Agora você pode ir em frente e atribuir a macro main a um botão como
fizemos no Capítulo 10. Embora o decorador xw.sub possa facilitar sua vida no Windows,
lembre-se de que, ao usá-lo, você perde a compatibilidade entre plataformas. Com xw.sub,
encontramos todos os decoradores xlwings — eu os resumi novamente na Tabela 12-1.
Para mais detalhes sobre esses decoradores, dê uma olhada na documentação do xlwings.
Conclusão
Este capítulo foi sobre como escrever funções em Python e importá-las para o Excel como
UDFs, permitindo que você as chame por meio de fórmulas de células. Ao trabalhar com o
estudo de caso do Google Trends, você aprendeu como influenciar o comportamento dos
argumentos da função e valores de retorno usando os decoradores arg e ret,
respectivamente. A última parte mostrou alguns truques de desempenho e apresentou o
decorador xw.sub, que você pode usar como substituto RunPython se estiver trabalhando
exclusivamente com o Windows. O bom de escrever UDFs em Python é que isso permite
substituir fórmulas de células longas e complexas por código Python que será mais fácil de
entender e manter.
Página 275 Capítulo 12
Minha maneira preferida de trabalhar com UDFs é definitivamente usar pandas
DataFrames com os novos arrays dinâmicos do Excel, uma combinação que facilita o
trabalho com o tipo de dados que obtemos do Google Trends, ou seja, DataFrames com um
número dinâmico de linhas.
E é isso – chegamos ao fim do livro! Muito obrigado pelo seu interesse na minha
interpretação de um ambiente moderno de automação e análise de dados para Excel! Minha
ideia era apresentar a você o mundo do Python e seus poderosos pacotes de código aberto,
permitindo que você escrevesse código Python para seu próximo projeto em vez de ter que
lidar com as próprias soluções do Excel, como VBA ou Power Query, mantendo assim uma
porta aberta para afaste-se do Excel se precisar. Espero poder dar alguns exemplos práticos
para facilitar o início. Depois de ler este livro, agora você sabe como:
• Substituir uma pasta de trabalho do Excel por um notebook Jupyter e código pandas
• Processe em lote as pastas de trabalho do Excel lendo-as com OpenPyXL, xlrd, pyxlsb
ou xlwings e, em seguida, consolide-as com pandas
• Produzir relatórios do Excel com OpenPyXL , XlsxWriter, xlwt ou xlwings
• Use o Excel como frontend e conecte-o a praticamente qualquer coisa que desejar via
xlwings, clicando em um botão ou escrevendo um UDF
Em breve, no entanto, você desejará ir além do escopo deste livro. Convido você a conferir
a página inicial do livro de tempos em tempos para atualizações e material adicional. Nesse
espírito, aqui estão algumas ideias que você pode explorar por conta própria:
Se este livro o inspirar a automatizar sua rotina diária ou semanal de baixar dados e copiá-
los/colar no Excel, eu não poderia estar mais feliz. A automação não apenas devolve o
tempo, mas também reduz drasticamente a chance de cometer erros.
Página 276 Capítulo 12
Se você tiver algum feedback, por favor, deixe-me saber sobre isso! Você pode entrar em
contato comigo via O'Reilly, abrindo um problema no repositório complementar ou no
Twitter em @felixzumstein.
Página 277
APÊNDICE A
Ambientes Conda
Ao pressionar Enter, o Conda imprimirá o que é vai instalar no novo ambiente e pede para
você confirmar:
Proceed ([y]/n)?
Página 278 Apêndice A
Pressione Enter para confirmar ou digite n se quiser cancelar. Após a instalação, ative seu
novo ambiente assim:
(base)> conda activate xl38
(xl38)>
O nome do ambiente mudou de base para xl38 e agora você pode usar Conda ou pip para
instalar pacotes neste novo ambiente sem afetar nenhum dos outros ambientes (lembrete:
use pip apenas se o pacote não estiver disponível via Conda). Vamos em frente e instalar
todos os pacotes deste livro na versão que eu estava usando. Primeiro, verifique se você está
no ambiente xl38, ou seja, o Prompt do Anaconda está sendo exibido (xl38), então instale
os pacotes Conda assim (o comando a seguir deve ser digitado como um único comando; as
quebras de linha são apenas para fins de exibição):
(xl38)> conda install lxml=4.6.1 matplotlib=3.3.2 notebook=6.1.4 openpyxl=3.0.5
pandas=1.1.3 pillow=8.0.1 plotly=4.14.1 flake8=3.8.4
python-dateutil=2.8.1 requests=2.24.0 sqlalchemy=1.3.20
xlrd=1.2.0 xlsxwriter=1.3.7 xlutils=2.0.0 xlwings=0.20.8
xlwt=1.3.0
Confirme o plano de instalação e finalize o ambiente instalando as duas dependências
restantes com pip:
(xl38)> pip install pyxlsb==1.0.7 pytrends==4.7.3
(xl38)>
Em vez de seguir os passos manualmente para criar o ambiente xl38, você também pode
aproveitar o arquivo de ambiente xl38.yml que incluí na pasta conda do repositório
complementar. A execução dos seguintes comandos cuida de tudo:
Página 279 Apêndice A
(base)> cd C:\Users\username\python-for-excel\conda
(base)> conda env create -f xl38.yml
(base)> conda activate xl38
(xl38)>
Por padrão, o Anaconda sempre ativa o base quando você abre um Terminal no macOS
ou o Prompt do Anaconda no Windows. Se você não gostar disso, você pode desativar a
ativação automática, como mostrarei a seguir.
macOS
No macOS, basta executar o seguinte comando no seu Terminal para desabilitar a
ativação automática:
(base)> conda config --set auto_activate_base false
Se você sempre quiser reverter, execute o mesmo comando novamente com true em
vez de false. As alterações entrarão em vigor após reiniciar o Terminal. Daqui para
frente, você precisará ativar o base assim antes de poder usar o comando python
novamente:
> conda activate base
(base)>
Página 280
APÊNDICE B
Funcionalidade avançada do VS
Code
Este apêndice mostra como o depurador funciona no VS Code e como você pode executar
notebooks Jupyter diretamente de dentro do VS Code. Os tópicos são independentes uns
dos outros, então você pode lê-los em qualquer ordem.
Depurador
Se você já usou o depurador VBA no Excel, tenho boas notícias para você: depurar com VS
Code é uma experiência muito semelhante. Vamos começar abrindo o arquivo
debugging.py do repositório complementar no VS Code. Clique na margem à esquerda da
linha número 4 para que apareça um ponto vermelho - este é o seu ponto de interrupção
onde a execução do código será pausada. Agora pressione F5 para iniciar a depuração: o
Painel de Comando aparecerá com uma seleção de configurações de depuração. Escolha
“Python File” para depurar o arquivo ativo e execute o código até atingir o ponto de
interrupção. A linha será destacada e a execução do código será pausada, veja a Figura B-1.
Enquanto você depura, a barra de status fica laranja.
Se a seção Variáveis não aparecer automaticamente à esquerda, certifique-se de clicar no
menu Executar para ver os valores das variáveis. Como alternativa, você também pode passar
o mouse sobre uma variável no código-fonte e obter uma dica de ferramenta com seu valor.
Na parte superior, você verá a barra de ferramentas de depuração que lhe dá acesso aos
seguintes botões da esquerda para a direita): Continue, Step Over, Step Into, Step Out,
Restart e Stop. Ao passar o mouse sobre eles, você também verá os atalhos de teclado.
Página 281 Apêndice B
Se você usa estruturas de dados como listas aninhadas, matrizes NumPy ou DataFrames,
você pode clicar duas vezes na variável: isso abrirá o Visualizador de Dados e fornecerá uma
visualização familiar semelhante a uma planilha. A Figura B-3 mostra o Visualizador de
Dados após clicar duas vezes na variável df.
Markdown cells precisa começar com # %% [markdown] e exigir que toda a célula seja
marcada como comentário. Se você deseja executar um arquivo como notebook, clique no
link “Executar abaixo” que aparece quando você passa o mouse sobre a primeira célula. Isso
abrirá a janela interativa do Python à direita, conforme mostrado na Figura B-4.
A Janela Interativa do Python é novamente mostrada como notebook. Para exportar seu
arquivo no formato ipynb, clique no ícone Salvar (Exportar como notebook Jupyter) na
parte superior da Janela Interativa do Python. A janela interativa do Python também oferece
uma célula na parte inferior de onde você pode executar o código interativamente. O uso de
arquivos Python regulares em oposição aos notebooks Jupyter permite que você use o
depurador do VS Code e facilita o trabalho com o controle de versão, pois as células de saída,
que normalmente adicionam muito ruído entre as versões, são ignoradas.
Página 285
APÊNDICE C
Conceitos avançados de Python
Neste apêndice, examinaremos mais de perto os três tópicos a seguir: classes e objetos,
objetos com reconhecimento de fuso horário datetime e objetos mutáveis versus imutáveis.
Os tópicos são independentes uns dos outros, então você pode lê-los em qualquer ordem.
Classes e objetos
Nesta seção, escreveremos nossa própria classe para entender melhor como classes e objetos
estão relacionados. As classes definem novos tipos de objetos: uma classe se comporta como
uma forma de mola que você usa para assar um bolo. Dependendo dos ingredientes que
você usa, você obtém um bolo diferente, por exemplo, um bolo de chocolate ou um
cheesecake. O processo de tirar um bolo (o objeto) do springform (a classe) é chamado
instanciação, razão pela qual os objetos também são chamados de instâncias de classe. Seja
chocolate ou cheesecake, ambos são um tipo de bolo: as classes permitem definir novos tipos
de dados que mantêm dados relacionados (atributos) e funções (métodos) juntos e,
portanto, ajudam a estruturar e organizar seu código. Deixe-me agora retornar ao exemplo
do jogo de corrida de carros do Capítulo 3 para definir nossa própria classe:
In [1]: class Car:
def __init__(self, color, speed=0):
self.color = color
self.speed = speed
Quando você chama uma classe, você está realmente chamando a função init, e é por isso
que tudo com relação aos argumentos da função também se aplica aqui: para car1,
fornecemos o argumento como argumento posicional, enquanto para car2, estamos
usando argumentos de palavra-chave. Depois de instanciar os dois objetos Car , vamos dar
uma olhada em seus atributos e chamar seus métodos. Como veremos, após acelerar car1,
a velocidade de car1 é alterada, mas permanece inalterada para car2 pois os dois objetos
são independentes um do outro:
In [3]: # By default, an object prints its memory location
car1
Out[3]: < main .Car at 0x7fea812e3890>
In [4]: # Attributes give you access to the data of an object
car1.color
Out[4]: 'red'
In [5]: car1.speed
Out[5]: 0
In [6]: # Calling the accelerate method on car1
car1.accelerate(20)
In [7]: # The speed attribute of car1 changed
car1.speed
Out[7]: 20
In [8]: # The speed attribute of car2 remained the same
car2.speed
Out[8]: 0
Python também permite que você altere atributos diretamente sem ter que usar métodos:
In [9]: car1.color = "green"
In [10]: car1.color
Out[10]: 'green'
In [11]: car2.color # inalterado
Página 287 Apêndice C
Out[11]: 'blue'
Para resumir: as classes definem os atributos e métodos dos objetos. As classes permitem que
você agrupe funções relacionadas (“métodos”) e dados (“atributos”) para que possam ser
acessados convenientemente via notação de ponto: myobject.attribute ou
myobject.method().
Saber sobre a diferença é importante, pois objetos mutáveis podem se comportar de maneira
diferente do que você está acostumado em outras linguagens, incluindo VBA. Dê uma
olhada no seguinte trecho de VBA:
Dim a As Variant, b As Variant
a = Array(1, 2, 3)
b = a
a(1) = 22
Debug.Print a(0) & ", " & a(1) & ", " & a(2)
Debug.Print b(0) & ", " & b(1) & ", " & b(2)
O que aconteceu aqui? Em Python, variáveis são nomes que você “anexa” a um objeto.
Fazendo b = a, você anexa os dois nomes ao mesmo objeto, a lista [1, 2, 3]. Todas as
variáveis anexadas a esse objeto, portanto, mostrarão as alterações na lista. No entanto, isso
só acontece com objetos mutáveis: se você substituir a lista por um objeto imutável como
uma tupla, alterar a não alteraria b. Se você quiser que um objeto mutável como b seja
independente das alterações em a, você deve copiar explicitamente a lista:
In [21]: a = [1, 2, 3]
b = a.copy()
In [22]: a
Out[22]: [1, 2, 3]
In [23]: b
Out[23]: [1, 2, 3]
In [24]: a[1] = 22 # Changing "a"...
In [25]: a
Out[25]: [1, 22, 3]
In [26]: b # ...doesn't affect "b"
Out[26]: [1, 2, 3]
Usando o método copy, você está criando uma cópia superficial: você obterá uma cópia
da lista, mas se a lista contiver elementos mutáveis, eles ainda serão compartilhados. Se você
deseja copiar todos os elementos recursivamente, você precisa fazer uma cópia profunda
usando o módulo copy da biblioteca padrão:
In [27]: import copy
b = copy.deepcopy(a)
Vamos agora ver o que acontece quando você usa objetos mutáveis como argumentos de
função.
No entanto, se você alterar ByRef da função increment para ByVal, ele imprimirá:
2
1
Como isso funciona em Python? Ao passar variáveis, você passa nomes que apontam para
objetos. Isso significa que o comportamento depende se o objeto é mutável ou não. Vamos
primeiro usar um objeto imutável:
In [28]: def increment(x):
x = x + 1
return x
In [29]: a = 1
print(increment(a))
print(a)
2
1
Outro caso a ser observado é o uso de objetos mutáveis como argumentos padrão nas
definições de funções — vamos ver o porquê!
Se você quiser usar uma lista vazia como argumento padrão, faça isso:
In [36]: def add_one(x=None):
if xis None:
x = []
x.append(1)
return x
In [37]: add_one()
Out[37]: [1]
In [38]: add_one()
Out[38]: [1]