100% found this document useful (4 votes)
836 views

Python For Excel Traduzido

Uploaded by

Renato
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (4 votes)
836 views

Python For Excel Traduzido

Uploaded by

Renato
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 297

Python

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.

DATA / PY THON Twitter: @oreillymedia


facebook.com/oreilly
US $59.99 CAN $79.99
ISBN: 978-1-492-08100-5
Praise for Python for Excel

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

Beijing Boston Farnham Sebastopol Tokyo


Python for Excel
by Felix Zumstein
Copyright © 2021 Zoomer Analytics LLC. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are
also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional
sales department: 800-998-9938 or [email protected].

Acquisitions Editor: Michelle Smith Indexer: nSight Inc.


Development Editor: Melissa Potter Interior Designer: David Futato
Production Editor: Daniel Elfanbaum Cover Designer: Karen Montgomery
Copyeditor: Piper Editorial Consulting, LLC Illustrator: Kate Dullea
Proofreader: nSight Inc.

March 2021: First Edition

Revision History for the First Edition


2021-03-04: First Release

See http://oreilly.com/catalog/errata.csp?isbn=9781492081005 for release details.

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

A Microsoft está executando um fórum de comentários para Excel no UserVoice onde


todos podem enviar uma nova ideia para outros votarem. A solicitação de recurso mais
votada é “Python como uma linguagem de script do Excel” e tem aproximadamente o dobro
de votos que a segunda solicitação de recurso mais votada. Embora nada realmente tenha
acontecido desde que a ideia foi adicionada em 2015, os usuários do Excel foram
alimentados com uma nova esperança no final de 2020, quando Guido van Rossum, o
criador do Python, twittou que sua “aposentadoria foi chata” e ele se juntaria à Microsoft.
Se a mudança dele tem alguma influência na integração do Excel e do Python, eu não sei.
Eu sei, no entanto, o que torna essa combinação tão atraente e como você pode começar a
usar o Excel e o Python juntos – hoje. E, em poucas palavras, é disso que trata este livro.
A principal força motriz por trás da história do Python para Excel é o fato de estarmos
vivendo em um mundo de dados. Hoje em dia, enormes conjuntos de dados estão
disponíveis para todos e sobretudo. Muitas vezes, esses conjuntos de dados são tão grandes
que não cabem mais em uma planilha. Alguns anos atrás, isso pode ter sido chamado de big
data, mas hoje em dia, um conjunto de dados de alguns milhões de linhas não é nada de
especial. O Excel evoluiu para lidar com essa tendência: introduziu o Power Query para
carregar e limpar conjuntos de dados que não cabem em uma planilha e o Power Pivot, um
suplemento para realizar análises de dados nesses conjuntos de dados e apresentar os
resultados. O Power Query é baseado na linguagem de fórmula M do Power Query (M),
enquanto o Power Pivot define fórmulas usando expressões de análise de dados (DAX). Se
você também deseja automatizar algumas coisas em seu arquivo do Excel, use a linguagem
de automação interna do Excel, Visual Basic for Applications (VBA). Ou seja, para algo
bastante simples, você pode acabar usando VBA, M e DAX. Um problema com isso é que
todas essas linguagens servem apenas para você no mundo da Microsoft, com mais destaque
no Excel e no Power BI (apresentarei o Power BI brevemente no Capítulo 1).
Python, por outro lado, é uma linguagem de programação de uso geral que se tornou uma
das escolhas mais populares entre analistas e cientistas de dados. Se você usa Python com
Excel, pode usar uma linguagem de programação que é boa em todos os aspectos da história,
seja automatizando o Excel, acessando e preparando conjuntos de dados ou executando
tarefas de análise e visualização de dados.
Página 2 Prefácio
Mais importante ainda, você pode reutilizar suas habilidades em Python fora do Excel: se
precisar aumentar seu poder de computação, poderá facilmente mover seu modelo
quantitativo, simulação ou aplicativo de aprendizado de máquina para a nuvem, onde
recursos de computação praticamente irrestritos estão esperando por você .

Por que escrevi este livro


Através do meu trabalho no xlwings, o pacote de automação do Excel que conheceremos
na Parte IV deste livro, estou em contato próximo com muitos usuários que usam Python
para Excel - seja por meio do rastreador de problemas no GitHub, uma pergunta no
StackOverflow ou em um evento físico como um encontro ou uma conferência.
Regularmente, me pedem para recomendar recursos para começar com o Python. Embora
certamente não haja escassez de introduções do Python, elas geralmente são muito gerais
(nada sobre análise de dados) ou muito específicas (introduções científicas completas). No
entanto, os usuários do Excel tendem a estar em algum lugar no meio: eles certamente
trabalham com dados, mas uma introdução científica completa pode ser muito técnica. Eles
também costumam ter requisitos e perguntas específicas que não são respondidas em
nenhum dos materiais existentes. Algumas dessas perguntas são:
• Qual pacote Python-Excel eu preciso para qual tarefa?
• Como faço para mover minha conexão de banco de dados do Power Query para o
Python?
• Qual é o equivalente do AutoFiltro do Excel ou da tabela dinâmica em Python?
Eu escrevi este livro para tirar você do zero conhecimento de Python para poder automatizar
suas tarefas centradas no Excel e aproveitar as ferramentas de análise de dados e computação
científica do Python no Excel sem nenhum desvio.

Para quem é este livro


Se você é um usuário avançado do Excel que deseja superar os limites do Excel com uma
linguagem de programação moderna, este livro é para você. Normalmente, isso significa que
você gasta horas todos os meses baixando, limpando e copiando/colando grandes
quantidades de dados em planilhas de missão crítica. Embora existam diferentes maneiras
de superar os limites do Excel, este livro se concentrará em como usar o Python para essa
tarefa.
Você deve ter uma compreensão básica de programação: ajuda se você já escreveu uma
função ou um loop for (não importa em qual linguagem de programação) e tenha uma ideia
sobre o que é um inteiro ou uma string. Você pode até dominar este livro se estiver
acostumado a escrever fórmulas de células complexas ou tiver experiência em ajustar macros
VBA gravadas. No entanto, você não deve ter nenhuma experiência específica em Python,
pois há introduções a todas as ferramentas que usaremos, incluindo uma introdução ao
próprio Python.
Página 3 Prefácio
Se você é um desenvolvedor de VBA experiente, encontrará comparações regulares entre
Python e VBA que permitirão que você contorne as pegadinhas comuns e dê o pontapé
inicial.
Este livro também pode ser útil se você for um desenvolvedor Python e precisar aprender
sobre as diferentes maneiras pelas quais o Python pode lidar com o aplicativo Excel e os
arquivos do Excel para poder escolher o pacote certo de acordo com os requisitos de seus
usuários de negócios.

Como este livro está organizado


Neste livro, mostrarei todos os aspectos da história do Python para Excel divididos em
quatro partes:
Parte I: Introdução ao Python
Esta parte começa examinando as razões pelas quais o Python é um companheiro tão
agradável para o Excel antes apresentando as ferramentas que usaremos neste livro: a
distribuição Anaconda Python, Visual Studio Code e notebooks Jupyter. Esta parte
também ensinará Python suficiente para dominar o restante deste livro.
Parte II: Introdução aos pandas
pandas é a biblioteca principal do Python para análise de dados. Aprenderemos como
substituir as pastas de trabalho do Excel por uma combinação de notebooks e pandas
do Jupyter. Normalmente, o código do pandas é mais fácil de manter e mais eficiente
do que uma pasta de trabalho do Excel, e você pode trabalhar com conjuntos de dados
que não cabem em uma planilha. Ao contrário do Excel, o pandas permite que você
execute seu código onde quiser, incluindo a nuvem.
Parte III: Lendo e gravando arquivos Excel sem Excel
Esta parte trata da manipulação de arquivos Excel usando um dos seguintes pacotes
Python: pandas, OpenPyXL, XlsxWriter, pyxlsb, xlrd e xlwt. Esses pacotes são capazes
de ler e gravar pastas de trabalho do Excel diretamente no disco e, como tal, substituem
o aplicativo Excel: como você não precisa de uma instalação do Excel, eles funcionam
em qualquer plataforma suportada pelo Python, incluindo Windows, macOS e Linux.
Um caso de uso típico para um pacote de leitor é ler dados de arquivos do Excel que
você recebe todas as manhãs de uma empresa ou sistema externo e armazenar seu
conteúdo em um banco de dados. Um caso de uso típico para um pacote de gravador é
fornecer a funcionalidade por trás do famoso botão “Exportar para Excel” que você
encontra em quase todos os aplicativos.
Parte IV: Programando o aplicativo Excel com xlwings
Nesta parte, veremos como podemos usar Python com o pacote xlwings para
automatizar o aplicativo Excel em vez de ler e gravar arquivos Excel em disco. Portanto,
esta parte requer que você tenha uma instalação local do Excel. Aprenderemos como
abrir pastas de trabalho do Excel e manipulá-las diante de nossos olhos.
Página 4 Prefácio
Além de ler e gravar arquivos via Excel, construiremos ferramentas interativas do Excel:
elas nos permitem clicar em um botão para que o Python execute algo que você pode
ter feito anteriormente com macros VBA, como um cálculo computacionalmente caro.
Também aprenderemos a escrever funções definidas pelo usuário1 (UDFs) em Python
em vez de VBA.
É importante entender a diferença fundamental entre ler e escrever arquivos (Parte III) e
programar o aplicativo (Parte IV) conforme visualizado na Figura P-1.

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.

Versões do Python e do Excel


Este livro é baseado no Python 3.8, que é a versão do Python que vem com a versão mais
recente da distribuição Anaconda Python no momento da redação deste artigo. Se você
quiser usar uma versão mais recente do Python, siga as instruções na página inicial do livro
page, mas certifique-se de não usar uma versão mais antiga. Ocasionalmente, farei um
comentário se algo mudar com o Python 3.9.
Este livro também espera que você use uma versão moderna do Excel, ou seja, pelo menos
Excel 2007 no Windows e Excel 2016 no macOS. A versão do Excel instalada localmente
que vem com a assinatura do Microsoft 365 também funcionará perfeitamente — na
verdade, eu até recomendo, pois possui os recursos mais recentes que você não encontrará
em outras versões do Excel. Essa também foi a versão que usei para escrever este livro,
portanto, se você usar outra versão do Excel, às vezes poderá ver uma pequena diferença no
nome ou no local de um item de menu.

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.

Este elemento significa uma dica ou sugestão.

Este elemento significa uma nota geral.

Este elemento indica um aviso ou cuidado.

Usando exemplos de código


Estou mantendo uma página da web com informações adicionais para ajudá-lo com este
livro. Certifique-se de verificar, especialmente se você tiver um problema.
O material suplementar (exemplos de código, exercícios etc.) está disponível para download
em https://github.com/fzumstein/python-for-excel. Para baixar este repositório
complementar, clique no botão verde Código e selecione Baixar ZIP. Uma vez baixado,
clique com o botão direito do mouse no arquivo no Windows e selecione Extrair tudo para
descompactar os arquivos contidos em uma pasta. No macOS, basta clicar duas vezes no
arquivo para descompactar.
Página 6 Prefácio
Se você sabe como trabalhar com o Git, também pode usar o Git para clonar o repositório
em seu disco rígido local. Você pode colocar a pasta onde quiser, mas vou me referir a ela
ocasionalmente da seguinte forma neste livro:
C:\Users\username\python-for-excel

Simplesmente baixando e descompactando o arquivo ZIP no Windows, você vai acabar


com uma estrutura de pastas semelhante a esta (observe os nomes das pastas repetidos):
C:\...\Downloads\python-for-excel-1st-edition\python-for-excel-1st-edition
Copiando o conteúdo desta pasta em um que você cria em
C:\Users\<username>\python-for-excel pode facilitar o acompanhamento. As mesmas
observações são válidas para o macOS, ou seja, copie os arquivos para
/Users/<username>/python-for-excel.
Se você tiver uma pergunta técnica ou um problema ao usar os exemplos de código, envie
um e-mail para [email protected].
Este livro está aqui para ajudá-lo a fazer o seu trabalho. Em geral, se um código de exemplo
for oferecido com este livro, você poderá usá-lo em seus programas e documentação. Você
não precisa entrar em contato conosco para obter permissão, a menos que esteja
reproduzindo uma parte significativa do código. Por exemplo, escrever um programa que
usa vários pedaços de código deste livro não requer permissão. Vender ou distribuir
exemplos de livros da O'Reilly requer permissão. Responder a uma pergunta citando este
livro e citando um código de exemplo não requer permissão. Incorporar uma quantidade
significativa de código de exemplo deste livro na documentação do seu produto requer
permissão.
Apreciamos, mas geralmente não exigimos, atribuição. Uma atribuição geralmente inclui o
título, autor, editora e ISBN. Por exemplo: “Python para Excel por Felix Zumstein
(O'Reilly). Copyright 2021 Zoomer Analytics LLC, 978-1-492-08100-5.”
Se você achar que o uso de exemplos de código está fora do uso justo ou da permissão dada
acima, sinta-se à vontade para nos contatar em [email protected].

O'Reilly Online Learning


Por mais de 40 anos, a O'Reilly Media forneceu treinamento em
tecnologia e negócios, conhecimento e visão para ajudar as
empresas a ter sucesso.
Nossa rede exclusiva de especialistas e inovadores compartilha seu conhecimento e
experiência por meio de livros, artigos e nossa plataforma de aprendizado online. A
plataforma de aprendizado on-line da O'Reilly oferece acesso sob demanda a cursos de
treinamento ao vivo, caminhos de aprendizado aprofundados, ambientes de codificação
interativos e uma vasta coleção de texto e vídeo da O'Reilly e mais de 200 outras editoras.
Para obter mais informações, visite http://oreilly.com.
Página 7 Prefácio
Como entrar em contato conosco
Envie comentários e perguntas sobre este livro para a editora:

O'Reilly Media, Inc.


1005 Gravenstein Highway North Sebastopol,
CA 95472
800-998-9938 (nos Estados Unidos ou Canadá)
707-829-0515 ( internacional ou local)
707-829-0104 (fax)

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?

Normalmente, os usuários do Excel começam a questionar suas ferramentas de planilha


quando atingem uma limitação. Um exemplo clássico é quando as pastas de trabalho do
Excel contêm tantos dados e fórmulas que se tornam lentas ou, na pior das hipóteses,
travam. No entanto, faz sentido questionar sua configuração antes que as coisas dêem
errado: se você trabalha em pastas de trabalho de missão crítica em que os erros podem
resultar em danos financeiros ou de reputação ou se você gasta horas todos os dias
atualizando manualmente as pastas de trabalho do Excel, você deve aprender como
automatize seus processos com uma linguagem de programação. A automação elimina o
risco de erro humano e permite que você gaste seu tempo em tarefas mais produtivas do que
copiar/colar dados em uma planilha do Excel.
Neste capítulo, apresentarei algumas razões pelas quais o Python é uma excelente escolha
em combinação com o Excel e quais são suas vantagens em comparação com a linguagem
de automação integrada do Excel, o VBA. Depois de apresentar o Excel como linguagem de
programação e entender suas particularidades, vou apontar os recursos específicos que
tornam o Python muito mais forte em comparação com o VBA. Para começar, no entanto,
vamos dar uma olhada rápida nas origens de nossos dois personagens principais!
Em termos de tecnologia de computador, o Excel e o Python existem há muito tempo: o
Excel foi lançado pela primeira vez em 1985 pela Microsoft - e isso pode ser uma surpresa
para muitos - estava disponível apenas para Apple Macintosh. Não foi até 1987 que o
Microsoft Windows obteve sua primeira versão na forma de Excel 2.0. A Microsoft não foi
o primeiro jogador no mercado de planilhas eletrônicas: VisiCorp saiu com VisiCalc em
1979, seguido por Lotus Software em 1983 com Lotus 1-2-3. E a Microsoft não liderou com
o Excel: três anos antes, eles lançaram o Multiplan, um programa de planilhas que poderia
ser usado no MS-DOS e em alguns outros sistemas operacionais, mas não no Windows.
Python nasceu em 1991, apenas seis anos depois do Excel. Embora o Excel tenha se tornado
popular desde o início, o Python demorou um pouco mais até ser adotado em certas áreas,
como desenvolvimento web ou administração de sistemas.
Página 10 Capítulo 1
Em 2005, o Python começou a se tornar uma alternativa séria para a computação científica
quando o NumPy, um pacote para computação baseada em array e álgebra linear, foi
lançado pela primeira vez. O NumPy combinou dois pacotes predecessores e, portanto,
simplificou todos os esforços de desenvolvimento em torno da computação científica em
um único projeto. Hoje, ele forma a base de inúmeros pacotes científicos, incluindo o
pandas, lançado em 2008 e que é o grande responsável pela ampla adoção do Python no
mundo da ciência de dados e finanças que começou a acontecer depois de 2010. Graças aos
pandas, o Python , juntamente com o R, tornou-se uma das linguagens mais usadas para
tarefas de ciência de dados, como análise de dados, estatísticas e aprendizado de máquina.
O fato de Python e Excel terem sido inventados há muito tempo não é a única coisa que eles
têm em comum: Excel e Python também são uma linguagem de programação. Embora você
provavelmente não fique surpreso ao ouvir isso sobre o Python, pode exigir uma explicação
para o Excel, que darei a seguir.

Excel é uma linguagem de programação


Esta seção começa apresentando o Excel como uma linguagem de programação, que o
ajudará a entender por que os problemas de planilhas aparecem regularmente nas notícias.
Em seguida, veremos algumas práticas recomendadas que surgiram na comunidade de
desenvolvimento de software e que podem evitar muitos erros típicos do Excel.
Concluiremos com uma breve introdução ao Power Query e Power Pivot, duas ferramentas
modernas do Excel que cobrem o tipo de funcionalidade para a qual usaremos pandas.
Se você usa o Excel para mais do que sua lista de compras, definitivamente está usando
funções como =SUM(A1:A4) para resumir um intervalo de células. Se você pensar um
pouco sobre como isso funciona, notará que o valor de uma célula geralmente depende de
uma ou mais outras células, que podem novamente usar funções que dependem de uma ou
mais outras células e assim por diante. Fazer essas chamadas de função aninhadas não é
diferente de como outras linguagens de programação funcionam, apenas que você escreve
o código em células em vez de arquivos de texto. E se isso ainda não o convenceu: no final
de 2020, a Microsoft anunciou a introdução das funções lambda, que permitem escrever
funções reutilizáveis na própria linguagem de fórmulas do Excel, ou seja, sem precisar
depender de uma linguagem diferente como o VBA . De acordo com Brian Jones, chefe de
produto do Excel, essa era a peça que faltava para finalmente tornar o Excel uma linguagem
de programação “real”.1 Isso também significa que os usuários do Excel deveriam realmente
ser chamados de programadores do Excel!
Há uma coisa especial, porém, sobre os programadores do Excel: a maioria deles são usuários
de negócios ou especialistas de domínio sem educação formal em ciência da computação.
São comerciantes, contadores ou engenheiros, para citar apenas alguns exemplos. Suas
ferramentas de planilhas são projetadas para resolver um problema de negócios e muitas
vezes ignoram as melhores práticas no desenvolvimento de software.

1 Você pode ler o anúncio das funções lambda no Blog do Excel.


Página 11 Capítulo 1
Como consequência, essas ferramentas de planilha geralmente misturam entradas, cálculos
e saídas nas mesmas planilhas, podem exigir a execução de etapas não óbvias para que
funcionem corretamente e alterações críticas são feitas sem qualquer rede de segurança. Em
outras palavras, as ferramentas de planilhas carecem de uma arquitetura de aplicativo sólida
e geralmente não são documentadas nem testadas. Às vezes, esses problemas podem ter
consequências devastadoras: se você esquecer de recalcular sua planilha de negociação antes
de fazer uma negociação, poderá comprar ou vender o número errado de ações, o que pode
fazer com que você perca dinheiro. E se não é apenas seu próprio dinheiro que você está
negociando, podemos ler sobre isso nas notícias, como veremos a seguir.

Excel nas notícias


O Excel é um convidado regular nas notícias e, durante a redação deste artigo, duas novas
histórias chegaram às manchetes. A primeira foi sobre o HUGO Gene Nomenclature
Committee, que renomeou alguns genes humanos para que não fossem mais interpretados
pelo Excel como datas. Por exemplo, para evitar que o gene MARCH1 se transformasse em 1-
Mar, ele foi renomeado para MARCHF1.2 Na segunda história, o Excel foi responsabilizado
pelo atraso na divulgação de 16.000 resultados de testes COVID-19 na Inglaterra. O
problema foi causado porque os resultados do teste foram gravados no formato de arquivo
do Excel mais antigo (.xls) que era limitado a aproximadamente 65.000 linhas. Isso
significava que conjuntos de dados maiores foram simplesmente cortados além desse limite.3
Embora essas duas histórias mostrem a importância e o domínio contínuos do Excel no
mundo de hoje, provavelmente não há outro “incidente do Excel” mais famoso do que o
London Whale.
London Whale é o apelido de um trader cujos erros de negociação forçaram o JP Morgan a
anunciar uma perda impressionante de US$ 6 bilhões em 2012. A fonte da explosão foi um
modelo de valor em risco baseado em Excel que estava subestimando substancialmente o
verdadeiro risco de perder dinheiro em uma de suas carteiras. O Relatório do JPMorgan
Chase & Co. Management Task Force Concerning 2012 CIO Losses4 (2013) menciona
que “o modelo operava por meio de uma série de planilhas Excel, que precisavam ser
preenchidas manualmente, por um processo de copiar e colar dados de uma planilha para
outro." Além desses problemas operacionais, eles tinham um erro lógico: em um cálculo,
eles estavam dividindo por uma soma em vez de uma média.
Se você quiser ver mais dessas histórias, dê uma olhada em Horror Stories, uma página da
web mantida pelo European Spreadsheet Risks Interest Group (EuSpRIG).

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.

Práticas recomendadas de programação


Esta seção apresentará as práticas recomendadas de programação mais importantes,
incluindo a separação de interesses, o princípio DRY, testes e controle de versão. Como
veremos, segui-los será mais fácil quando você começar a usar o Python junto com o Excel.

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.

Figura 1-1. currency_converter.xlsx

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!

Python para Excel


Excel tem tudo a ver com armazenamento, análise e visualização de dados. E como o Python
é particularmente forte na área de computação científica, é um ajuste natural em
combinação com o Excel. Python também é uma das poucas linguagens que atrai tanto o
programador profissional quanto o usuário iniciante que escreve algumas linhas de código
a cada poucas semanas. Os programadores profissionais, por um lado, gostam de trabalhar
com Python porque é uma linguagem de programação de uso geral e, portanto, permite que
você consiga praticamente qualquer coisa sem pular obstáculos. Iniciantes, por outro lado,
gostam de Python porque é mais fácil de aprender do que outras linguagens. Como
consequência, o Python é usado para análise de dados ad hoc e tarefas de automação
menores, bem como em grandes bases de código de produção, como o back-end do
Instagram.6 Isso também significa que, quando sua ferramenta Excel baseada em Python se
tornar realmente popular, será fácil adicionar um desenvolvedor da Web ao projeto que
transformará seu protótipo Excel-Python em um aplicativo da Web completo.

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.

Biblioteca padrão e gerenciador de pacotes


Python vem com um rico conjunto de funcionalidades integradas fornecidas por sua
biblioteca padrão. A comunidade Python gosta de se referir a ele dizendo que o Python vem
com “baterias incluídas”. Se você precisa descompactar um arquivo ZIP, ler os valores de
um arquivo CSV ou buscar dados da Internet, a biblioteca padrão do Python o cobre, e
você pode conseguir tudo isso em apenas algumas linhas de código. A mesma
funcionalidade no VBA exigiria que você escrevesse uma quantidade considerável de código
ou instalasse um suplemento. E muitas vezes, as soluções que você encontra na internet só
funcionam no Windows, mas não no macOS.
Embora a biblioteca padrão do Python cubra uma quantidade impressionante de
funcionalidades, ainda existem tarefas que são complicadas de programar ou lentas quando
você está confiando apenas na biblioteca padrão. É aqui que o PyPI PyPI significa Python
Package Index e é um repositório gigante onde todos (incluindo você!) podem fazer upload
de pacotes Python de código aberto que adicionam funcionalidades adicionais ao Python.
PyPI vs. PyPy
PyPI é pronunciado “olho de ervilha torta”. Isso é para diferenciar o PyPI do PyPy,
que é pronunciado “pie pie” e que é uma implementação alternativa rápida do
Python.

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.

Software de código aberto (OSS)


Neste ponto, gostaria de dizer algumas palavras sobre código aberto, já que usei essa palavra
algumas vezes nesta seção. Se o software é distribuído sob uma licença de código aberto, isso
significa que seu código-fonte está disponível gratuitamente, permitindo que todos
contribuam com novas funcionalidades, correções de bugs ou documentação. O próprio
Python e quase todos os pacotes Python de terceiros são de código aberto e mais comumente
mantidos por desenvolvedores em seu tempo livre. Isso nem sempre é um estado ideal: se
sua empresa depende de determinados pacotes, você tem interesse no desenvolvimento e
manutenção contínuos desses pacotes por programadores profissionais. Felizmente, a
comunidade científica do Python reconheceu que alguns pacotes são importantes demais
para deixar seu destino nas mãos de alguns voluntários que trabalham à noite e nos fins de
semana.
Por isso em 2012, NumFOCUS, uma organização sem fins lucrativos, foi criada para
patrocinar vários pacotes e projetos Python na área de computação científica. Os pacotes
Python mais populares patrocinados pelo NumFOCUS são pandas, NumPy, SciPy,
Matplotlib e Project Jupyter, mas hoje em dia eles também suportam pacotes de várias
outras linguagens, incluindo R, Julia e JavaScript. Existem alguns grandes patrocinadores
corporativos, mas todos podem se juntar ao NumFOCUS como membros gratuitos da
comunidade – as doações são dedutíveis de impostos.

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.

Recursos de linguagem moderna


Desde o Excel 97, a linguagem VBA não sofreu grandes alterações em termos de recursos de
linguagem. Isso, no entanto, não significa que o VBA não seja mais suportado: a Microsoft
está enviando atualizações com cada nova versão do Excel para poder automatizar os novos
recursos do Excel introduzidos com essa versão. Por exemplo, o Excel 2016 adicionou
suporte para automatizar o Power Query. Uma linguagem que parou de evoluir há mais de
vinte anos está perdendo os conceitos de linguagem moderna que foram introduzidos em
todas as principais linguagens de programação ao longo dos anos. Como exemplo, o
tratamento de erros no VBA está realmente mostrando sua idade. Se você quiser lidar com
um erro normalmente no VBA, é algo assim:
Sub PrintReciprocal(number As Variant)
' There will be an error if the number is 0 or a string
On Error GoTo ErrorHandler
result = 1 / number
On Error GoTo 0
Debug.Print "There was no error!"
Finally:
' Runs whether or not an error occurs
If result = "" Then
result = "N/A"
End If
Debug.Print "The reciprocal is: " & result
Exit Sub
ErrorHandler:
' Runs only in case of an error
Debug.Print "There was an error: " & Err.Description
Resume Finally
End Sub
Página 23 Capítulo 1
O tratamento de erros do VBA envolve o uso de labels como Finally e ErrorHandler
no exemplo. Você instrui o código a pular para esses rótulos por meio das instruções GoTo
ou Resume. Desde o início, as gravadoras foram reconhecidas como responsáveis pelo que
muitos programadores chamariam de spaghetti code: uma boa maneira de dizer que o fluxo
do código é difícil de seguir e, portanto, difícil de manter. É por isso que praticamente todas
as linguagens desenvolvidas ativamente introduziram o mecanismo try/catch— em
Python chamado try/except — que apresentarei no Capítulo 11. Se você é um
desenvolvedor proficiente em VBA, também pode gostar do fato de o Python suportar
herança de classe, um recurso da programação orientada a objetos que está faltando no
VBA.
Além dos recursos de linguagem moderna, há outro requisito para uma linguagem de
programação moderna: compatibilidade entre plataformas. Vamos ver porque isso é
importante!

Compatibilidade entre plataformas


Mesmo que você desenvolva seu código em um computador local executado no Windows
ou macOS, é muito provável que você queira executar seu programa em um servidor ou na
nuvem em algum momento. Os servidores permitem que seu código seja executado em um
cronograma e tornam seu aplicativo acessível de qualquer lugar, com o poder de
computação que você precisa. Na verdade, mostrarei como executar código Python em um
servidor no próximo capítulo, apresentando a você os notebooks Jupyter hospedados. A
grande maioria dos servidores roda em Linux, pois é um sistema operacional estável, seguro
e econômico. E como os programas Python são executados inalterados em todos os
principais sistemas operacionais, isso eliminará muito da dor quando você fizer a transição
de sua máquina local para uma configuração de produção.
Por outro lado, mesmo que o Excel VBA seja executado no Windows e no macOS, é fácil
introduzir funcionalidades que são executadas apenas no Windows. Na documentação
oficial do VBA ou em fóruns, muitas vezes você verá códigos como este:
Set fso = CreateObject("Scripting.FileSystemObject")

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

Figura 2-1. Ambiente de desenvolvimento

O Anaconda Python Distribution


Anaconda é sem dúvida a distribuição Python mais popular usada para ciência de dados e
vem com centenas de pacotes de terceiros pré-instalados: inclui notebooks Jupyter e a
maioria dos outros pacotes que este livro usará extensivamente, incluindo pandas,
OpenPyXL e xlwings. O Anaconda Individual Edition é gratuito para uso privado e garante
que todos os pacotes incluídos são compatíveis entre si. Ele é instalado em uma única pasta
e pode ser facilmente desinstalado novamente. Após instalá-lo, aprenderemos alguns
comandos básicos no Prompt do Anaconda e executaremos uma sessão interativa do
Python. Em seguida, conheceremos os gerenciadores de pacotes Conda e pip antes de
encerrar esta seção com ambientes Conda. Vamos começar baixando e instalando o
Anaconda!

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.

Prompt do Anaconda sem o Anaconda


Se você não usa a distribuição do Anaconda Python, terá que usar o Prompt de
Comando no Windows e o Terminal no macOS sempre que eu instruir você a
usar o Prompt do Anaconda.

Se você nunca usou um prompt de comando no Windows ou um terminal no macOS, não


se preocupe: você só precisa conhecer um punhado de comandos que já lhe darão muito
poder. Depois de se acostumar, usar o Prompt do Anaconda geralmente é mais rápido e
conveniente do que clicar nos menus gráficos do usuário. Vamos começar:
Windows
Clique no botão do menu Iniciar e comece a digitar Anaconda Prompt. Nas entradas
que aparecem, escolha Anaconda Prompt, não Anaconda Powershell Prompt.
Selecione-o com as setas do teclado e pressione Enter ou use o mouse para clicar nele.
Se você preferir abri-lo através do menu Iniciar, você o encontrará no Anaconda3. É
uma boa ideia fixar o Prompt do Anaconda na barra de tarefas do Windows, pois você
o usará regularmente ao longo deste livro. A linha de entrada do Anaconda Prompt
começará com (base):
(base) C:\Users\felix>
Página 28 Capítulo 2
macOS
No macOS, você não encontrará um aplicativo chamado Anaconda Prompt. Em vez
disso, por Anaconda Prompt, estou me referindo ao Terminal que foi configurado pelo
instalador do Anaconda para ativar automaticamente um ambiente Conda (falarei
mais sobre ambientes Conda em breve): pressione Command-Space bar ou abra o
Launchpad, em seguida, digite Terminal e pressione Enter. Como alternativa, abra o
Finder e navegue até Aplicativos > Utilitários, onde você encontrará o aplicativo
Terminal no qual você pode clicar duas vezes. Assim que o Terminal aparecer, ele deve
se parecer com isso, ou seja, a linha de entrada deve começar com (base):
(base) felix@MacBook-Pro ~ %

Se você estiver em uma versão mais antiga do macOS, será mais ou menos assim :
(base) MacBook-Pro:~ felix$

Ao contrário do Prompt de Comando no Windows, o Terminal no macOS não mostra


o caminho completo do diretório atual. Em vez disso, o til representa o diretório inicial,
que geralmente é /Users/<username>. Para ver o caminho completo do seu diretório
atual, digite pwd seguido de Enter. pwd significa diretório de trabalho de impressão.
Se a linha de entrada em seu Terminal não iniciar com (base) após a instalação do
Anaconda, aqui está um motivo comum: se você tinha o Terminal rodando durante a
instalação do Anaconda, você precisará reiniciá-lo. Observe que clicar na cruz vermelha
no canto superior esquerdo da janela do Terminal apenas a ocultará, mas não a fechará.
Em vez disso, clique com o botão direito do mouse no Terminal no dock e selecione
Sair ou pressione Command-Q enquanto o Terminal for sua janela ativa. Quando você
inicia novamente e o Terminal mostra (base) no início de uma nova linha, está tudo
pronto. É uma boa ideia manter o Terminal em seu dock, pois você o usará
regularmente ao longo deste livro.
Com o Prompt do Anaconda em execução, experimente os comandos descritos na Tabela
2-1. Estou explicando cada comando com mais detalhes após a tabela.

Tabela 2-1. Comandos para o Prompt do Anaconda


Comando Windows macOS
Listar arquivos no diretório atual dir ls -la
Alterar diretório (relativo) cd path\to\dir cd path/to/dir
Alterar diretório (absoluto) cd C:\path\to\dir cd / path/to/dir
Muda para a unidade D D: (não existe)
Muda para o diretório pai cd .. cd ..
Percorre os comandos anteriores ↑ (seta para cima) ↑ (seta para cima)
Página 29 Capítulo 2
Listar arquivos no diretório atual
No Windows , digite dir para o diretório e pressione Enter. Isso imprimirá o
conteúdo do diretório em que você está atualmente.
No macOS, digite ls -la seguido de Enter. ls é a abreviação de list directory
contents e -la imprimirá a saída no formato de listagem longa e incluirá todos os
arquivos, incluindo os ocultos.
Alterar diretório
Digite cd Down e pressione a tecla Tab. cd significa mudança de diretório. Se você
estiver em sua pasta pessoal, o Prompt do Anaconda provavelmente poderá completá-
lo automaticamente para cd Downloads. Se você estiver em uma pasta diferente ou não
tiver uma pasta chamada Downloads, simplesmente comece a digitar o início de um
dos nomes de diretório que você viu com o comando anterior ( dir ou ls -la) antes
de pressionar a tecla Tab para completar automaticamente. Em seguida, pressione
Enter para mudar para o diretório de preenchimento automático. Se você estiver no
Windows e precisar alterar sua unidade, primeiro precisará digitar o nome da unidade
antes de poder alterar para o diretório correto:
C:\Users\felix> D:
D:\> cd data
D:\data>
Observe que ao iniciar seu caminho com um diretório ou nome de arquivo que esteja
dentro do diretório atual, você está usando um caminho relativo, por exemplo, cd
Downloads. Se você quiser sair do seu diretório atual, você pode digitar um caminho
absoluto, por exemplo, cd C:\Users no Windows ou cd /Users no macOS (lembre-
se da barra no início).
Mudar para o diretório pai
Para ir para o diretório pai, ou seja, um nível acima na hierarquia de diretórios, digite
cd .. seguido de Enter (certifique-se de que há um espaço entre cd e os pontos). Você
pode combinar isso com um nome de diretório, por exemplo, se quiser subir um nível
e, em seguida, mudar para o Desktop, digite cd ..\Desktop. No macOS, substitua a
barra invertida por uma barra.
Percorrer os comandos anteriores
Use a tecla de seta para cima para percorrer os comandos anteriores. Isso economizará
muitos pressionamentos de tecla se você precisar executar os mesmos comandos
repetidamente. Se você rolar muito, use a tecla de seta para baixo para rolar para trás.

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".

E já é isso! Agora você pode iniciar o Prompt do Anaconda e executar comandos no


diretório desejado. Você usará isso imediatamente na próxima seção, onde mostrarei como
iniciar uma sessão interativa do Python.

Python REPL: uma sessão interativa do Python


Você pode iniciar uma sessão interativa do Python executando o python em um prompt
do Anaconda:
(base) C:\Users\felix>python
Python 3.8.5 (default, Sep 3 2020, 21:29:08) [...] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

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.

Notação de Prompt do Anaconda


Daqui para frente, iniciarei linhas de código com (base)> para denotar que eles
são digitados em um prompt do Anaconda. Por exemplo, para iniciar um
interpretador Python interativo, escreverei:
(base)> python
que no Windows será semelhante a este:
(base) C:\Users\felix> python
e no macOS semelhante a este (lembre-se, no macOS , o Terminal é seu Prompt do
Anaconda):
(base) felix@MacBook-Pro ~ % python
Página 31 Capítulo 2
Vamos brincar um pouco! Observe que >>> em uma sessão interativa significa que o
Python espera sua entrada; você não precisa digitar isso. Continue digitando cada linha que
começa com >>> e confirme com a tecla Enter:
>>> 3 + 4
7
>>> "python " * 3
'python python python '

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.

Gerenciadores de Pacotes: Conda e pip


Já falei algumas palavras sobre o pip, o gerenciador de pacotes do Python no capítulo
anterior: o pip cuida de baixar, instalar, atualizar e desinstalar pacotes do Python, bem como
suas dependências e subdependências. Embora o Anaconda funcione com pip, ele possui
um gerenciador de pacotes alternativo integrado chamado Conda. Uma vantagem do
Conda é que ele pode instalar mais do que apenas pacotes Python, incluindo versões
adicionais do interpretador Python. Como uma breve recapitulação: os pacotes adicionam
funcionalidades adicionais à sua instalação do Python que não são cobertas pela biblioteca
padrão. pandas, que apresentarei apropriadamente no Capítulo 5, é um exemplo de tal
pacote. Como ele vem pré-instalado na instalação do Python do Anaconda, você não precisa
instalá-lo manualmente.

Conda vs. pip


Com o Anaconda, você deve instalar tudo o que puder via Conda e usar apenas o
pip para instalar os pacotes que o Conda não consegue encontrar. Caso contrário,
o Conda pode substituir arquivos que foram instalados anteriormente com pip.
Página 32 Capítulo 2
Tabela 2-2 fornece uma visão geral dos comandos que você usará com mais frequência. Esses
comandos devem ser digitados em um prompt do Anaconda e permitirão que você instale,
atualize e desinstale seus pacotes de terceiros.

Tabela 2-2. Comandos Conda e pip


Ação Conda pip
Listar todos os pacotes instalados conda list pip freeze
Instalar a versão mais recente do conda install package pip install package
pacote
Instalar uma versão específica do conda install pip install
pacote package=1.0.0 package==1.0.0

Atualizar um pacote conda update package pip install --upgrade


package
Desinstala um pacote conda remove pacote pip uninstall package

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

Ao contrário do Conda, o pip instalará os pacotes imediatamente quando você pressionar


Enter sem a necessidade de confirmar.

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!

Executando Jupyter Notebooks


No Prompt do Anaconda, mude para o diretório do seu repositório complementar e inicie
um servidor de notebook Jupyter:
(base)> cd C:\Users\username\python-for-excel
(base)> notebook jupyter

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

Figura 2-2. O painel Jupyter

Isso abrirá uma nova guia do navegador com seu primeiro notebook Jupyter vazio,
conforme mostrado na Figura 2-3.

Figura 2-3. Um notebook Jupyter vazio

É 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

## Este é um título de segundo nível

Você pode deixar seu texto *itálico* ou * *negrito** ou 'monoespaçado'.

* Este é um ponto de bala


* Este é outro ponto de bala
Página 37 Capítulo 2
Depois de pressionar Shift+Enter, o texto será renderizado em HTML bem formatado.
Neste ponto, seu notebook deve se parecer com o que está na Figura 2-4. As células de
redução também permitem incluir imagens, vídeos ou fórmulas; veja os notebook Jupyter.

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 .

Editar vs. Modo de comando


Quando você interage com células em um notebook Jupyter, você está no Modo edição ou
no modo de comando:
Modo de edição
Clicar em uma célula inicia o modo de edição: a borda ao redor da célula selecionada
fica verde e o cursor na célula está piscando. Em vez de clicar em uma célula, você
também pode pressionar Enter quando a célula for selecionada.
Modo de comando
Para alternar para o modo de comando, pressione a tecla Escape; a borda ao redor da
célula selecionada será azul e não haverá nenhum cursor piscando. Os atalhos de teclado
mais importantes que você pode usar no modo de comando são mostrados na Tabela
2-3.
Página 38 Capítulo 2
Tabela 2-3. Atalhos de teclado (modo de comando)
Atalho Ação
Shift+Enter Executa a célula (funciona também no modo de edição)
↑ (seta para cima) Move o seletor de células para cima

↓ (seta para baixo) Move o seletor de células para baixo


b Insere uma nova célula abaixo da célula atual
a Insira uma nova célula acima da célula atual
dd Exclua a célula atual (digite duas vezes a letra d)
m Altere o tipo de célula para Markdown
y Altere o tipo de célula para código

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.

Questões de ordem de execução


Como os notebooks são fáceis e fáceis de usar para começar, eles também facilitam a entrada
em estados confusos se você não executar as células sequencialmente. Suponha que você
tenha as seguintes células de notebook que são executadas de cima para baixo:
In [2]: a = 1
In [3]: a
Out[3]: 1
In [4]: a = 2

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.

Notebooks Jupyter na nuvem


Os notebooks Jupyter tornaram-se tão populares que são oferecidos como uma solução
hospedada por vários provedores de nuvem. Estou apresentando três serviços aqui que são
todos de uso gratuito. A vantagem desses serviços é que eles são executados
instantaneamente e em qualquer lugar você pode acessar um navegador, sem a necessidade
de instalar nada localmente. Você pode, por exemplo, executar as amostras em um tablet
enquanto lê as três primeiras partes. Como Parte IV requer uma instalação local do Excel,
isso não funcionará lá.
Fichário
Fichário é um serviço fornecido pelo Projeto Jupyter, a organização por trás dos
notebooks Jupyter. O Binder destina-se a experimentar os notebooks Jupyter de
repositórios Git públicos - você não armazena nada no próprio Binder e, portanto, não
precisa se inscrever ou fazer login para usá-lo.
Kaggle Notebooks
Kaggle é uma plataforma para ciência de dados. Como ele hospeda competições de
ciência de dados, você obtém acesso fácil a uma enorme coleção de conjuntos de dados.
Kaggle faz parte do Google desde 2017.
Google Colab
Google Colab (abreviação de Colaboratory) é a plataforma de notebooks do Google.
Infelizmente, a maioria dos atalhos de teclado do notebook Jupyter não funciona, mas
você pode acessar arquivos no seu Google Drive, incluindo o Planilhas Google.
A maneira mais fácil de executar os notebooks Jupyter do repositório complementar na
nuvem é acessando a URL do Binder. Você estará trabalhando em uma cópia do repositório
complementar, então fique à vontade para editar e quebrar coisas como quiser!
Página 40 Capítulo 2
Agora que sabemos como trabalhar com notebooks Jupyter, vamos seguir em frente e
aprender sobre como escrever e executar scripts Python padrão. Para fazer isso, usaremos o
Visual Studio Code, um poderoso editor de texto com excelente suporte ao Python.

Visual Studio Code


Nesta seção, instalaremos e configuraremos o Visual Studio Code (VS Code), um editor
de texto gratuito e de código aberto da Microsoft. Depois de apresentar seus componentes
mais importantes, escreveremos um primeiro script Python e o executaremos de algumas
maneiras diferentes. Para começar, no entanto, explicarei quando usaremos os notebooks
Jupyter em vez de executar scripts Python e por que escolhi o VS Code para este livro.
Embora os notebooks Jupyter sejam incríveis para fluxos de trabalho interativos, como
pesquisar, ensinar e experimentar, eles são menos ideais se você deseja escrever scripts
Python voltados para um ambiente de produção que não precisa dos recursos de
visualização dos notebooks. Além disso, projetos mais complexos que envolvem muitos
arquivos e desenvolvedores são difíceis de gerenciar com notebooks Jupyter. Nesse caso,
você deseja usar um editor de texto adequado para escrever e executar arquivos Python
clássicos. Em teoria, você poderia usar praticamente qualquer editor de texto (até o Bloco
de Notas funcionaria), mas, na realidade, você quer um que “entenda” Python. Ou seja, um
editor de texto que suporte pelo menos os seguintes recursos:
Realce de sintaxe
O editor colore as palavras de maneira diferente, dependendo se elas representam uma
função, uma string, um número etc. Isso facilita muito a leitura e compreensão do
código.
Autocomplete
Autocomplete ou IntelliSense, como a Microsoft o chama, sugere automaticamente
componentes de texto para que você precise digitar menos, o que leva a menos erros.
E em breve, você terá outras necessidades que gostaria de acessar diretamente de dentro do
editor:
Executar código
Alternar entre o editor de texto e um Prompt Anaconda externo (ou seja, Prompt de
Comando ou Terminal) para executar seu código pode ser um problema.
Depurador
Um depurador permite percorrer o código linha por linha para ver o que está
acontecendo.
Controle de versão
Se você usa o Git para controlar a versão de seus arquivos, faz sentido lidar com as coisas
relacionadas ao Git diretamente no editor para que você não precise alternar entre dois
aplicativos.
Página 41 Capítulo 2
Existe um amplo espectro de ferramentas que podem ajudá-lo com tudo isso e, como de
costume, cada desenvolvedor tem necessidades e preferências diferentes. Alguns podem
realmente querer usar um editor de texto sem frescuras junto com um prompt de comando
externo. Outros podem preferir um ambiente de desenvolvimento integrado (IDE): IDEs
tentam colocar tudo o que você precisa em uma única ferramenta, o que pode deixá-los
inchados.
Escolhi o VS Code para este livro, pois ele rapidamente se tornou um dos editores de código
mais populares entre os desenvolvedores após seu lançamento inicial em 2015: no
StackOverflow Developer Survey 2019, ele se tornou o ambiente de desenvolvimento mais
popular. O que torna o VS Code uma ferramenta tão popular? Em essência, é a mistura
certa entre um editor de texto básico e um IDE completo: o VS Code é um mini IDE que
vem com tudo o que você precisa para programação pronta para uso, mas não mais:
multiplataforma
VSO código é executado no Windows, macOS e Linux. Também existem versões
hospedadas na nuvem, como GitHub Codespaces.
Ferramentas integradas
VS Code vem com um depurador, suporte para controle de versão Git e possui um
Terminal integrado que você pode usar como Anaconda Prompt.
Extensões
Todo o resto, por exemplo, suporte a Python, é adicionado por meio de extensões que
podem ser instaladas com um único clique.
Leve
Dependendo do seu sistema operacional, o instalador do VS Code tem apenas 50–100
MB.

Visual Studio Code versus Visual Studio


Não confunda Visual Studio Code com Visual Studio, o IDE! Embora você possa
usar o Visual Studio para desenvolvimento em Python (ele vem com PTVS, as
Ferramentas Python para Visual Studio), é uma instalação muito pesada e
tradicionalmente usada para trabalhar com linguagens .NET como C#.

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.

Figura 2-5. Barra de Atividades do Visual Studio Code


No lado esquerdo, você vê a Barra de Atividades com os seguintes ícones de cima para
baixo:
• Explorer
• Pesquisar
• Controle de Origem
Página 43 Capítulo 2
• Executar
• Extensões
Barra de Status
Na parte inferior do editor, você tem a Barra de Status. Depois de concluir a
configuração e editar um arquivo Python, você verá o interpretador Python aparecer
lá.
Paleta de Comandos
Você pode mostrar a Paleta de Comandos via F1 ou com o atalho de teclado Ctrl
+Shift+P (Windows) ou Command-Shift-P (macOS). Se você não tiver certeza sobre
algo, sua primeira parada deve ser sempre a Paleta de Comandos, pois ela oferece acesso
fácil a quase tudo o que você pode fazer com o VS Code. Por exemplo, se você estiver
procurando por atalhos de teclado, digite keyboard shortcuts, selecione a entrada
“Ajuda: Referência de atalhos de teclado” e pressione Enter.
O VS Code é um ótimo editor de texto pronto para uso, mas para que funcione bem com
o Python, há mais algumas coisas para configurar: clique no ícone Extensions na Activity
Bar e procure por Python. Instale a extensão oficial do Python que mostra a Microsoft como
o autor. Levará um momento para instalar e, uma vez feito, pode ser necessário clicar no
botão Recarregar necessário para concluir - como alternativa, você também pode reiniciar o
VS Code completamente. Finalize a configuração de acordo com sua plataforma:
Windows
Abra a Paleta de Comandos e digite default shell. Selecione a entrada que diz
“Terminal: Select Default Shell” e pressione Enter. No menu suspenso, selecione
Prompt de Comando e confirme pressionando Enter. Isso é necessário porque, caso
contrário, o VS Code não pode ativar adequadamente os ambientes Conda.
macOS
Abra a Paleta de Comandos e digite shell command. Selecione a entrada que diz
“Comando Shell: Instalar o comando 'código' no PATH” e pressione Enter. Isso é
necessário para que você possa iniciar o VS Code convenientemente a partir do Prompt
do Anaconda (ou seja, o Terminal).
Agora que o VS Code está instalado e configurado, vamos usá-lo para escrever e executar
nosso primeiro script Python!

Executando um script Python


Embora você possa abrir o VS Code por meio do menu Iniciar no Windows ou Launchpad
no macOS, geralmente é mais rápido abrir o VS Code no Prompt do Anaconda, onde você
pode iniciá-lo por meio do code. Portanto, abra um novo Prompt do Anaconda e mude
para o diretório em que deseja trabalhar usando o comando cd, em seguida, instrua o VS
Code a abrir o diretório atual (representado pelo ponto):
Página 44 Capítulo 2
(base)> cd C:\Users\username\python-for-excel
(base)> code .

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!")

Lembre-se que os notebooks Jupyter convenientemente imprimem o valor de retorno da


última linha automaticamente? Ao executar um script Python tradicional, você precisa
informar explicitamente ao Python o que imprimir, e é por isso que você precisa usar a
função print aqui. Na barra de status, você deve ver agora sua versão do Python, por
exemplo, “Python 3.8.5 64-bit (conda)”. Se você clicar nele, a Paleta de Comandos será
aberta e permitirá que você selecione um interpretador Python diferente se você tiver mais
de um (isso inclui ambientes Conda). Sua configuração agora deve se parecer com a da
Figura 2-6.

Figura 2-6. Código VS com hello_world.py aberto


Página 45 Capítulo 2
Antes de podermos executar o script, salve-o pressionando Ctrl+S no Windows ou
Command-S no macOS. Com os notebooks Jupyter, poderíamos simplesmente selecionar
uma célula e pressionar Shift+Enter para executar essa célula. Com o VS Code, você pode
executar seu código no Prompt do Anaconda ou clicando no botão Executar. Executar
código Python a partir do Prompt do Anaconda é como você provavelmente executa scripts
que estão em um servidor, portanto, é importante saber como isso funciona.
Prompt do Anaconda
Abra um Prompt do Anaconda, cd no diretório com o script e execute o script assim:
(base)> cd C:\Users\username\python-for-excel
(base)> python hello_world.py
hello world!A última linha é a saída que é impressa pelo script. Observe que, se você
não estiver no mesmo diretório que seu arquivo Python, precisará usar o caminho
completo para seu arquivo Python:
(base)> python C:\Users\username\python-for-excel\hello_world.py
hello world!

Caminhos de arquivos longos no prompt do Anaconda


Uma maneira conveniente de lidar com caminhos de arquivos longos é arrastar e
soltar o arquivo no prompt do Anaconda. Isso escreverá o caminho completo onde
quer que o cursor esteja.

Prompt do Anaconda no VS Code


Você não precisa sair do VS Code para trabalhar com o Prompt do Anaconda: o VS
Code tem um Terminal integrado que você pode mostrar através do atalho de teclado
Ctrl+` ou via View > Terminal. Como ele abre na pasta do projeto, você não precisa
alterar o diretório primeiro:
(base)> python hello_world.py
hello world!

Botão Executar no código


VS No código VS, há uma maneira fácil de executar seu código sem ter que usar o
Prompt do Anaconda: quando você edita um arquivo Python, você verá um ícone
Reproduzir verde no canto superior direito — este é o arquivo Executar botão, como
mostrado na Figura 2-6. Clicar nele abrirá o Terminal na parte inferior
automaticamente e executará o código lá.
Página 46 Capítulo 2
Abrindo arquivos no VS Code
O VS Code tem um comportamento padrão não convencional quando você clica
uma vez em um arquivo no Explorer (Barra de atividades): o arquivo é aberto no
modo de visualização, o que significa que o próximo arquivo que você clicar uma
vez substituirá na guia, a menos que você tenha feito algumas alterações no
arquivo. Se você deseja desativar o comportamento de clique único (para que um
clique único selecione um arquivo e um clique duplo o abra), vá para Preferências
> Configurações (Ctrl+, no Windows ou Command-, no macOS) e defina o menu
suspenso em Workbench> “List: Open Mode” para “doubleClick”.

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.

Editores de texto alternativos e IDEs


Ferramentas são algo individual, e só porque este livro é baseado em notebooks Jupyter e VS
Code não significa que você não deva dar uma olhada em outras opções.
Alguns editores de texto populares incluem:
Sublime Text
Sublime é um editor de texto comercial rápido.
Notepad++
Notepad++ é gratuito e existe há muito tempo, mas é apenas para Windows.
Vim ou Emacs
Vim ou Emacs podem não ser as melhores opções para programadores iniciantes devido
à sua curva de aprendizado, mas são muito populares entre os profissionais. A rivalidade
entre os dois editores gratuitos é tão grande que a Wikipedia a descreve como a “guerra
dos editores”.
IDEs populares incluem:
PyCharm
O PyCharm a edição da comunidade é gratuita e muito poderosa, enquanto a edição
profissional é comercial e adiciona suporte para ferramentas científicas e
desenvolvimento web.
Spyder
Spyder é semelhante ao IDE do MATLAB e vem com um explorador de variáveis.
Como está incluído na distribuição do Anaconda, você pode experimentá-lo
executando o seguinte em um Prompt do Anaconda: (base)> spyder.
Página 47 Capítulo 2
JupyterLab
JupyterLab é um IDE baseado na web desenvolvido pela equipe por trás dos notebooks
Jupyter e pode, é claro, executar notebooks Jupyter. Fora isso, ele tenta integrar tudo o
que você precisa para suas tarefas de ciência de dados em uma única ferramenta.
Wing Python IDE
Wing Python IDE é um IDE que existe há muito tempo. Existem versões simplificadas
gratuitas e uma versão comercial chamada Wing Pro.
Komodo IDE
Komodo IDE é um IDE comercial desenvolvido pela ActiveState e suporta muitas
outras linguagens além do Python.
PyDev
PyDev é um IDE Python baseado no popular Eclipse IDE.

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

Ao contrário do VBA, o Python diferencia maiúsculas de minúsculas, então a e A são duas


variáveis diferentes. Os nomes de variáveis devem seguir certas regras:

• Eles devem começar com uma letra ou um sublinhado


• Eles devem consistir em letras, números e sublinhados

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

Excel Cells Always Store Floats


Você pode precisar converter um float em um int ao ler um número de uma
célula do Excel e fornecê-la como um argumento para uma função Python que
espera um número inteiro. A razão é que os números nas células do Excel são
sempre armazenados como floats nos bastidores, mesmo que o Excel mostre o que
parece ser um número inteiro.

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.

Imprecisões de ponto flutuante


Por padrão, o Excel geralmente mostra números arredondados: digite =1.125-1.1 em uma
célula e você verá 0.025. Embora isso possa ser o que você espera, não é o que o Excel
armazena internamente. Altere o formato de exibição para mostrar pelo menos 16 decimais
e ele mudará para 0,0249999999999999. Este é o efeito da imprecisão do ponto flutuante:
os computadores vivem em um mundo binário, ou seja, eles calculam apenas com zeros e
uns. Certas frações decimais como 0,1 não podem ser armazenadas como um número
binário finito de ponto flutuante, o que explica o resultado da subtração. No Python, você
verá o mesmo efeito, mas o Python não oculta os decimais de você:
In [8]: 1.125 - 1.1
Out[8]: 0.02499999999999991

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

A maioria dos editores tem um atalho de teclado para comentar/descomentar linhas . Em


notebooks Jupyter e VS Code, é Ctrl+/ (Windows) ou Command-/ (macOS). Observe que
as células Markdown em notebooks Jupyter não aceitarão comentários — se você iniciar
uma linha com um # , o Markdown interpretará isso como um título.

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.

Para verificar novamente se um objeto é True ou False, use o construtor bool:


In [25]: bool(2)
Out[25]: True
In [26]: bool(0)
Página 54 Capítulo 3
Out[26]: False
In [27]: bool("some text") # We'll get to strings in a moment
Out[27]: True
In [28]: bool("")
Out[28]: False
In [29]: bool(None)
Out[29]: False

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.

Figura 3-1. Indexação do início e do fim de uma sequência

Traps de erro comuns para desenvolvedores de VBA


Se você vem do VBA, a indexação é um trap de erro comum. O VBA usa indexação
baseada em um para a maioria das coleções, como planilhas ( Sheets(1)), mas usa
indexação baseada em zero para matrizes (MyArray(0)), embora esse padrão possa
ser alterado. Outra diferença é que o VBA usa parênteses para indexação enquanto
o Python usa colchetes.

A sintaxe para indexação é a seguinte:


sequence[index]

Assim, você acessa elementos específicos de uma string como esta:


In [36]: language = "PYTHON"
In [37]: language[0]
Out[37]: 'P'
In [38]: language[1]
Out[38]: 'Y'
In [39]: language[-1]
Out[39]: 'N'
In [40]: language[-2]
Out[40]: 'O'

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.

Você pode alterar elementos nas


listas:
In [56]: users = ["Linda", "Brian"]
In [57]: users.append("Jennifer") # Most commonly you add to the end
users
Out[57]: ['Linda', 'Brian', 'Jennifer']
In [58]: users.insert(0, "Kim") # Insert "Kim" at index 0
users
Out[58]: ['Kim', 'Linda', 'Brian', 'Jennifer']

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']

Note que você também pode usar len e in com strings:


In [66]: len("Python")
Out[66]: 6
In [67]: "free" in "Python is free and open source."
Out[67]: True

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, ...

Usar parênteses geralmente facilita a leitura:


In [76]: currencies = ("EUR", "GBP", "AUD")
Tuplas permitem que você para acessar os elementos da mesma forma que as listas, mas eles
não permitirão que você altere os elementos. Em vez disso, concatenar tuplas criará uma
nova tupla nos bastidores e, em seguida, vinculará sua variável a esta nova tupla:
In [77]: currencies[0] # Accessing the first element
Out[77]: 'EUR'
In [78]: # Concatenating tuples will return a new tuple.
currencies + ("SGD",)
Out[78]: ('EUR', 'GBP', 'AUD', 'SGD')

Eu explico a diferença entre objetos mutáveis e imutáveis em detalhes no Apêndice,


C mas para agora, vamos dar uma olhada na última estrutura de dados desta seção: sets.

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']

Tabela 3-1. Estruturas de Dados


Estrutura de dados Literais Construtor
Lista [1, 2, 3] list((1, 2, 3))
Dicionário {"a": 1, "b": 2} dict(a=1, b=2)
Tupla ( 1, 2, 3) tuple([1, 2, 3])
Set {1, 2, 3} set((1, 2, 3))

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.

Blocos de código e a instrução pass


Um bloco de código define uma seção em seu código-fonte que é usada para algo especial.
Por exemplo, você usa um bloco de código para definir as linhas sobre as quais seu programa
está em loop ou compõe a definição de uma função. Em Python, você define blocos de
código recuando-os, não usando palavras-chave como no VBA ou chaves como na maioria
das outras linguagens.
Página 64 Capítulo 3
Isso é chamado de espaço em branco significativo. A comunidade Python estabeleceu
quatro espaços como recuo, mas você geralmente os digita pressionando a tecla Tab: tanto
os notebooks Jupyter quanto o VS Code converterão automaticamente sua tecla Tab em
quatro espaços. Deixe-me mostrar como os blocos de código são formalmente definidos
usando a instrução if:
if condition:
pass # Do nothing

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.

A instrução if e expressões condicionais


Para apresentar a instrução if, deixe-me reproduzir o exemplo de “Legibilidade e
Manutenibilidade” na página 13 no Capítulo 1, mas desta vez em Python:
In [85]: i = 20
if i < 5:
print("i is smaller than 5")
elif i <= 10:
print("i is between 5 and 10")
else:
print("i is bigger than 10")
i is bigger than 10

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.

Programadores vindos de outras linguagens frequentemente escreveriam algo como if


(is_important == True) ou if len(values) > 0 em vez disso.
Expressões condicionais, também chamadas de operadores ternários, permitem que você
use um estilo mais compacto para instruções simples if/else:
In [88]: is_important = False
print("important") if is_important else print("not important")
not important

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.

Os Loops for e while


Se você precisar fazer algo repetidamente como imprimir o valor de dez variáveis diferentes,
você está fazendo um grande favor a si mesmo ao não copiar/colar a instrução print dez
vezes. Em vez disso, use um loop for fazer o trabalho para você. Loops for iterar sobre os
itens de uma sequência como uma lista, uma tupla ou uma string (lembre-se, strings são
sequências de caracteres). Como exemplo introdutório, vamos criar um for que pegue cada
elemento da lista currencies, atribua-o à variável currency e imprima-o — um após o
outro até que não haja mais elementos na lista:
In [89]: currencies = ["USD", "HKD", "AUD"]

for currency in currencies:


print(currency)

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

currencies = Array("USD", "HKD", "AUD")


Página 66 Capítulo 3
For Each curr In currencies
Debug.Print curr
Next

Em Python, se você precisar de uma variável de contador em um loop for, os embutidos


range ou enumerate pode te ajudar com isso. Vejamos primeiro range, que fornece uma
sequência de números: você a chama fornecendo um único argumento stop ou fornecendo
um argumento start e stop, com um argumento opcional step. Como com corte,
start é inclusivo, stop é exclusivo e step determina o tamanho do passo, com 1 sendo
o padrão:
range(stop)
range(start, stop, step)

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)

Converter o intervalo em uma lista resolve este problema:


In [91]: list(range(5)) # stop argument
Out[91]: [0, 1, 2, 3, 4]
In [92]: list(range(2, 5, 2)) # start, stop, step arguments
Out[92]: [2, 4]

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

Para sair de um loop, use a instrução break:


In [97]: for i in range(15):
if i == 2:
break
else:
print(i)
0
1

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 Python, ele se comporta como você provavelmente esperaria:


In [99]: for i in range(1, 4):
print(i)
print(i)
1
2
3
3

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.

Listas, dicionários e compreensões de conjuntos


Listas, dicionários e compreensões de conjuntos são tecnicamente uma maneira de criar a
respectiva estrutura de dados, mas geralmente substituem um loop for, e é por isso que os
estou apresentando aqui. Suponha que na lista a seguir de pares de moedas USD, você
gostaria de escolher as moedas em que o USD é cotado como a segunda moeda. Você pode
escrever o seguinte loop for:
In [101]: currency_pairs = ["USDJPY", "USDGBP", "USDCHF",
"USDCAD", "AUDUSD", "NZDUSD"]
Página 69 Capítulo 3
In [102]: usd_quote = []
for pair in currency_pairs:
if pair[3:] == "USD":
usd_quote.append(pair[:3])
usd_quote
Out[102]: ['AUD', 'NZD']

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']

Com dicionários, existem compreensões de dicionário:


In [105]: exchange_rates = {"EURUSD": 1.1152,
"GBPUSD": 1.2454,
"AUDUSD": 0.6161}
{k: v * 100 for (k, v) in exchange_rates.items()}
Out[105]: {'EURUSD': 111.52, 'GBPUSD': 124.54, 'AUDUSD': 61.61}

E com conjuntos, há conjuntos de compreensão:


In [106]: {s + "USD" for s in ["EUR", "GBP", "EUR", "HKD", "HKD"]}
Out[106]: {'EURUSD', 'GBPUSD', 'HKDUSD'}

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.

Módulos e a instrução de importação


Quando você escreve código para projetos maiores, você terá que dividi-lo em diferentes
arquivos em algum ponto para poder trazê-lo para uma estrutura sustentável. Como já
vimos no capítulo anterior, os arquivos Python têm a extensão .py e normalmente você se
refere ao seu arquivo principal como um script. Se agora você deseja que seu script principal
acesse a funcionalidade de outros arquivos, você precisa importar essa funcionalidade
primeiro. Nesse contexto, os arquivos de origem do Python são chamados de módulos. Para
ter uma ideia melhor de como isso funciona e quais são as diferentes opções de importação,
dê uma olhada no arquivo temperature.py no repositório complementar, abrindo-o com o
VS Code (Exemplo 3-1). Se você precisar de uma atualização sobre como abrir arquivos no
VS Code, dê outra olhada no Capítulo 2.
Página 72 Capítulo 3
Exemplo 3-1. temperature.py

TEMPERATURE_SCALES = ("fahrenheit", "kelvin", "celsius")

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}"

print("This is the temperature module."

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.

Os módulos são importados apenas uma vez


Se você executar a import temperature novamente, notará que ela não imprime
mais nada. Isso ocorre porque os módulos Python são importados apenas uma vez
por sessão. Se você alterar o código em um módulo que você importa, você precisa
reiniciar seu interpretador Python para pegar todas as alterações, ou seja, em um
notebook Jupyter, você teria que clicar em Kernel > Restart.
Página 73 Capítulo 3
Na realidade, você normalmente não imprime nada em módulos. Isso foi apenas para
mostrar o efeito de importar um módulo mais de uma vez. Mais comumente, você coloca
funções e classes em seus módulos (para mais informações sobre classes, veja o Apêndice C).
Se você não quiser digitar temperature toda vez que usar um objeto do módulo
temperature, altere a instrução import assim:
In [114]: import temperature as tp
In [115]: tp.TEMPERATURE_SCALES
Out[115]: ('fahrenheit', 'kelvin', 'celsius')

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.

Não nomeie seus scripts como pacotes existentes


Uma fonte comum de erros é nomear seu arquivo Python da mesma forma que um
pacote ou módulo Python existente. Se você criar um arquivo para testar algumas
funcionalidades do pandas, não chame esse arquivo de pandas.py, pois isso pode
causar conflitos.
Página 74 Capítulo 3
Agora que você sabe como funciona o mecanismo de importação, vamos usá-lo
imediatamente para importar o módulo datetime! Isso também permitirá que você
aprenda mais algumas coisas sobre objetos e classes.

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.

PEP 8: Guia de estilo para código Python


Você deve estar se perguntando por que às vezes eu usava nomes de variáveis com
sublinhados ou em letras maiúsculas. Esta seção explicará minhas opções de formatação
apresentando o guia de estilo oficial do Python. Python usa as chamadas Propostas de
Aprimoramento do Python (PEP) para discutir a introdução de novos recursos de
linguagem. Um deles, o Guia de Estilo para Código Python, geralmente é referido por seu
número: PEP 8. PEP 8 é um conjunto de recomendações de estilo para a comunidade
Python; se todos que trabalham no mesmo código aderirem ao mesmo guia de estilo, o
código se tornará muito mais legível. Isso é especialmente importante no mundo do código
aberto, onde muitos programadores trabalham no mesmo projeto, muitas vezes sem se
conhecerem pessoalmente. Exemplo 3-2 mostra um pequeno arquivo Python que apresenta
as convenções mais importantes.

Exemplo 3-2. pep8_sample.py

"""This script shows a few PEP 8 rules.


"""
Página 76 Capítulo 3
import datetime as dt

TEMPERATURE_SCALES = ("fahrenheit", "kelvin",


"celsius")

class TemperatureConverter:
pass # Doesn't do anything at the moment

def convert_to_celsius(degrees, source="fahrenheit"):


"""This function converts degrees Fahrenheit or Kelvin
into degrees Celsius.
"""
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}"

celsius = convert_to_celsius(44, source="fahrenheit")


non_celsius_scales = TEMPERATURE_SCALES[:-1]

print("Current time: " + dt.datetime.now().isoformat())


print(f"The temperature in Celsius is: {celsius}")

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.

Separe classes e funções com duas linhas vazias do resto do código.


Página 77 Capítulo 3
Apesar do fato de que muitas classes como datetime são todas minúsculas, suas
próprias classes devem usar CapitalizedWords como nomes. Para mais informações
sobre classes, veja o Apêndice C.

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.

Funções e argumentos de função devem usar nomes em minúsculas com sublinhados


se melhorarem a legibilidade. Não use espaços entre o nome do argumento e seu valor
padrão.

A docstring de uma função também deve listar e explicar os argumentos da função.


Não fiz isso aqui para manter a amostra curta, mas você encontrará docstrings
completas no arquivo excel.py que está incluído no repositório complementar e que
encontraremos no Capítulo 8.

Não use espaços ao redor dos dois pontos.

Use espaços em torno de operadores matemáticos. Se forem usados operadores com


prioridades diferentes, você pode considerar adicionar espaços apenas em torno
daqueles com a prioridade mais baixa. Como a multiplicação neste exemplo tem a
prioridade mais baixa, adicionei espaços ao redor dela.

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.

Com indexação e fatiamento, não use espaços ao redor dos colchetes.


Este é um resumo simplificado do PEP 8, então é uma boa ideia dar uma olhada no PEP 8
uma vez que você começa a ficar mais sério com o Python. O PEP 8 afirma claramente que
é uma recomendação e que seus próprios guias de estilo terão precedência. Afinal, a
consistência é o fator mais importante. Se você estiver interessado em outras diretrizes
disponíveis publicamente, você pode dar uma olhada no guia de estilo do Google para
Python, que é razoavelmente próximo do PEP 8. Na prática, a maioria dos programadores
Python aderem vagamente ao PEP 8 e ignoram o comprimento máximo da linha de 79
caracteres é provavelmente o pecado mais comum.
Como pode ser difícil formatar seu código corretamente ao escrevê-lo, você pode verificar
seu estilo automaticamente. A próxima seção mostra como isso funciona com o VS Code.
Página 78 Capítulo 3
PEP 8 e VS Code
Ao trabalhar com o VS Code, existe uma maneira fácil de garantir que seu código permaneça
no PEP 8: use um linter. Um linter verifica seu código-fonte quanto a erros de sintaxe e
estilo. Abra a paleta de comandos (Ctrl+Shift+P no Windows ou Command-Shift-P no
macOS) e procure por Python: selecione Linter. Uma opção popular é o flake8, um pacote
que vem pré-instalado com o Anaconda. Se ativado, o VS Code sublinhará os problemas
com linhas onduladas toda vez que você salvar seu arquivo. Passar o mouse sobre uma linha
tão ondulada fornecerá uma explicação em uma dica de ferramenta. Você desativa um linter
novamente pesquisando por “Python: Enable Linting” na paleta de comandos e escolhendo
“Disable Linting”. Se preferir, você também pode executar flake8 em um prompt do
Anaconda para imprimir um relatório (o comando só imprime algo se houver uma violação
do PEP 8, portanto, executar isso em pep8_sample.py não imprimirá nada a menos que
você introduza uma violação ):
(base)> cd C:\Users\username\python-for-excel
(base)> flake8 pep8_sample.py

Python recentemente levou a análise de código estático um passo adiante, adicionando


suporte para dicas de tipo. A próxima seção explica como eles funcionam.

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

def hello(name: str) -> str:


return f"Hello {name}!"

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.

Figura 4-1. Um array unidimensional e bidimensional NumPy

Vamos criar um array unidimensional e bidimensional para trabalhar ao longo deste


capítulo:
In [3]: # Primeiro, vamos importar NumPy
import numpy as np
In [4]: # Construindo um array com uma lista simples resulta em um array 1d
array1 = np.array([10, 100, 1000.])
Em [5]: # Construir um array com uma lista aninhada resulta em um array 2d
array2 = np.array([[1., 2., 3.],
[4., 5., 6.]])

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.]])

Para realizar multiplicações de matrizes ou ponto produtos, use o operador @:1


Em [11]: array2 @ array2.T # array2.T é um atalho para array2.transpose()
Out[11]: array([[14., 32.],
[32., 77.]])

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.

Figura 4-2. Cálculos baseados em matrizes no Excel

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.

Funções universais (ufunc)


As funções universais (ufunc) funcionam em todos os elementos de uma matriz NumPy.
Por exemplo, se você usar a função raiz quadrada padrão do Python do módulo math em
um array NumPy, você receberá um erro:
In [12]: import math
In [13]: math.sqrt(array2) # Isso gerará um erro

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

TypeError: only size-1 arrays can be converted to Python scalars


Você pode, é claro, escrever um loop aninhado para obter a raiz quadrada de cada elemento,
então construir um array NumPy novamente do resultado:
In [14]: np.array([[math.sqrt(i) for i in row] for row in array2])
Out[14]: array([[1. , 1.41421356, 1.73205081],
[2. , 2.23606798, 2.44948974]])

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.

Criando e Manipulando Arrays


Vou começar esta seção obtendo e configurando elementos específicos de um array antes de
apresentar alguns construtores de array úteis, incluindo um para criar números pseudo-
aleatórios que você pode usar para uma simulação de Monte Carlo. Vou encerrar esta seção
explicando a diferença entre uma visão e uma cópia de um array.
Página 85 Capítulo 4
Obtendo e definindo elementos de matriz
No capítulo anterior, mostrei como indexar e fatiar listas para obter acesso a elementos
específicos. Quando você trabalha com listas aninhadas como matriz do primeiro exemplo
deste capítulo, você pode usar a indexação encadeada: matrix[0][0] obterá o primeiro
elemento da primeira linha. Com arrays NumPy, no entanto, você fornece os argumentos
de índice e fatia para ambas as dimensões em um único par de colchetes:
numpy_array[row_selection, column_selection]

Para arrays unidimensionais, isso simplifica para numpy_array[selection]. Ao selecionar


um único elemento, você receberá de volta um escalar; caso contrário, você receberá de volta
uma matriz uni ou bidimensional. Lembre-se de que a notação de fatia usa um índice inicial
(incluído) e um índice final (excluído) com dois pontos entre eles, como em start:end. Ao
deixar de lado o índice inicial e final, você fica com dois pontos, que, portanto, representam
todas as linhas ou todas as colunas em uma matriz bidimensional. Visualizei alguns
exemplos na Figura 4-3, mas você também pode querer dar Figura 4-1 , já que os índices e
eixos estão rotulados lá. Lembre-se, ao fatiar uma coluna ou linha de uma matriz
bidimensional, você acaba com uma matriz unidimensional, não com uma coluna ou vetor
de linha bidimensional!

Figura 4-3. Selecionando elementos de um array NumPy

Brinque com os exemplos mostrados na Figura 4-3 executando o seguinte código:


In [18]: array1[2] # Retorna um escalar
Out[18]: 1000.0
In [19]: array2[0, 0] # Retorna um escalar
Out[19]: 1.0
In [20]: array2[:, 1:] # Retorna um array 2d
Out[20]: array([[2., 3.],
[5., 6.]])
In [21]: array2[:, 1] # Retorna um array 1d
Out[21]: array([2., 5.])
In [22]: array2[1, :2] # Retorna a 1d array
Out[22]: array([4., 5.])
Página 86 Capítulo 4
Até agora, construí os arrays de amostra manualmente, ou seja, fornecendo números em
uma lista. Mas o NumPy também oferece algumas funções úteis para construir arrays.

Construtores de Array Úteis


O NumPy oferece algumas maneiras de construir arrays que também serão úteis para criar
DataFrames de pandas, como veremos no Capítulo 5. Uma maneira de criar arrays
facilmente é usar a função arange. Isso significa intervalo de array e é semelhante ao
range que encontramos no capítulo anterior - com a diferença de que arange retorna um
array NumPy. Combiná-lo com reshape nos permite gerar rapidamente um array com as
dimensões desejadas:
In [23]: np.arange(2 * 5).reshape(2, 5) # 2 rows, 5 columns
Out[23]: array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])

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.

Ver vs. Copiar


Arrays NumPy retornam visualizações quando você os divide. Isso significa que você está
trabalhando com um subconjunto da matriz original sem copiar os dados. Definir um valor
em uma visualização, portanto, também alterará o array original:
In [25]: array2
Out[25]: array([[1., 2., 3.],
[4., 5., 6.]])
In [26]: subset = array2[:, :2]
subset
Out[26]: array([[1., 2.],
[4., 5.]])
In [27]: subset[0, 0] = 1000
In [28]: subset
Out[28]: array([[1000., 2.],
[ 4., 5.]])
Página 87 Capítulo 4
In [29]: array2
Out[29]: array([[1000., 2., 3.],
[ 4., 5., 6.]])

Se não for isso que você deseja, você teria que alterar In [26] da seguinte forma:
subset = array2[:, :2].copy()

Trabalhar em uma cópia deixará o array original inalterado.

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

Este capítulo apresentará a você os pandas, a Biblioteca de Análise de Dados Python ou


— como eu gosto de dizer — a planilha baseada em Python com superpoderes. É tão
poderoso que algumas das empresas com as quais trabalhei conseguiram se livrar do Excel
completamente, substituindo-o por uma combinação de notebooks Jupyter e pandas.
Como leitor deste livro, no entanto, suponho que você manterá o Excel, caso em que os
pandas servirão como uma interface para obter e retirar dados de planilhas. pandas torna as
tarefas que são particularmente dolorosas no Excel mais fáceis, rápidas e menos propensas a
erros. Algumas dessas tarefas incluem obter grandes conjuntos de dados de fontes externas
e trabalhar com estatísticas, séries temporais e gráficos interativos. Os super poderes mais
importantes dos pandas são a vetorização e o alinhamento de dados. Como já vimos no
capítulo anterior com arrays NumPy, a vetorização permite que você escreva código conciso
e baseado em array, enquanto o alinhamento de dados garante que não haja
incompatibilidade de dados ao trabalhar com vários conjuntos de dados.
Este capítulo abrange toda a jornada de análise de dados: começa com a limpeza e preparação
dos dados antes de mostrar como entender conjuntos de dados maiores por meio de
agregação, estatísticas descritivas e visualização. No final do capítulo, veremos como
podemos importar e exportar dados com pandas. Mas antes de mais nada — vamos começar
com uma introdução às principais estruturas de dados dos pandas: DataFrame e Series!

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.

Figura 5-1. Uma série de pandas e DataFrame

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.

Figura 5-2. course_participants.xlsx

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

O Função read_excel com Python 3.9


Se você estiver executando pd.read_excel com Python 3.9 ou superior,
certifique-se de usar pelo menos pandas 1.2 ou você receberá um erro ao ler os
arquivos xlsx.
Página 90 Capítulo 5
Se você executar isso em um notebook Jupyter, o DataFrame será bem formatado como
uma tabela HTML, o que o torna ainda mais próximo da aparência da tabela no Excel. Vou
gastar todo o Capítulo 7 lendo e escrevendo arquivos Excel com pandas, então este foi
apenas um exemplo introdutório para mostrar que planilhas e DataFrames são, de fato,
muito semelhantes. Vamos agora recriar este DataFrame do zero sem lê-lo do arquivo Excel:
uma maneira de criar um DataFrame é fornecer os dados como uma lista aninhada,
juntamente com valores para columns e index:
In [3]: data=[["Mark", 55, "Italy", 4.5, "Europe"],
["John", 33, "USA", 6.7, "America"],
["Tim", 41, "USA", 3.9, "America"],
["Jenny", 12, "Germany", 9.0, "Europe"]]
df = pd.DataFrame(data=data,
columns=["name", "age", "country",
"score", "continent"],
index=[1001, 1000, 1002, 1003])
df
Out[3]: 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

Ao chamar o método info, você obterá algumas informações básicas, principalmente o


número de pontos de dados e os dados tipos para cada coluna:
In [4]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 4 entries, 1001 to 1003
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 name 4 non-null object
1 age 4 non-null int64
2 country 4 non-null object
3 score 4 non-null float64
4 continent 4 non-null object
dtypes: float64(1), int64(1), object(3)
memory usage: 192.0+ bytes

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

Ao contrário da chave primária de um banco de dados, um índice DataFrame pode ter


duplicatas, mas pesquisar valores pode ser mais lento nesse caso. Para transformar um índice
em uma coluna regular, use reset_index, e para definir um novo índice, use set_index.
Se você não quiser perder seu índice existente ao definir um novo, certifique-se de redefini-
lo primeiro:
In [7]: # "reset_index" turns the index into a column, replacing the
# index with the default index. This corresponds to the DataFrame
# from the beginning that we loaded from Excel.
df.reset_index()
Out[7]: 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
In [8]: # "reset_index" turns "user_id" into a regular column and
# "set_index" turns the column "name" into the index
df.reset_index().set_index("name")
Out[8]: user_id age country score continent
name
Mark 1001 55 Italy 4.5 Europe
John 1000 33 USA 6.7 America
Tim 1002 41 USA 3.9 America
Jenny 1003 12 Germany 9.0 Europe
Página 92 Capítulo 5
Fazendo df.reset_index().set_index ("name") , você está usando o encadeamento de
métodos: como reset_index() retorna um DataFrame, você pode chamar diretamente
outro método DataFrame sem precisar escrever o resultado intermediário primeiro.

Métodos DataFrame Retornam Cópias


Sempre que você chamar um método em um DataFrame no formato
df.method_name(), você receberá de volta uma cópia do DataFrame com aquele
método aplicado, deixando o DataFrame original intocado. Acabamos de fazer isso
chamando df.reset_index(). Se você quisesse alterar o DataFrame original, teria
que atribuir o valor de retorno de volta à variável original como a seguir:
df = df.reset_index()
Como não estamos fazendo isso, significa que nossa variável df ainda está
mantendo seu dados originais. Os próximos exemplos também chamam os
métodos DataFrame, ou seja, não alteram o DataFrame original.

Para alterar o índice, use o método reindex:


In [9]: df.reindex([999, 1000, 1001, 1004])
Out[9]: name age country score continent
user_id
999 NaN NaN NaN NaN NaN
1000 John 33.0 USA 6.7 America
1001 Mark 55.0 Italy 4.5 Europe
1004 NaN NaN NaN NaN NaN

Esta é uma primeiro exemplo de alinhamento de dados em funcionamento: reindex


assumirá todas as linhas que correspondem ao novo índice e introduzirá linhas com valores
ausentes (NaN) onde não houver informações. Os elementos de índice que você deixar de
lado serão descartados. Vou apresentar o NaN corretamente um pouco mais adiante neste
capítulo. Finalmente, para ordenar um índice, use o método sort_index:
In [10]: df.sort_index()
Out[10]: name age country score continent
user_id
1000 John 33 USA 6.7 America
1001 Mark 55 Italy 4.5 Europe
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ê não gostar da coluna nomes, renomeie-os:


In [14]: df.rename(columns={"name": "First Name", "age": "Age"})
Out[14]: properties First 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.

Selecionando por rótulo


A forma mais comum de acessar os dados de um DataFrame é consultando seus rótulos. Use
o atributo loc, que significa location, para especificar quais linhas e colunas você deseja
recuperar:
df.loc[row_selection, column_selection]

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.

Tabela 5-1. Seleção de dados por rótulo


Seleção Tipo de dados de retorno Exemplo
Valor único Scalar df.loc[1000, "country"]
Uma coluna (1d) Series df.loc[:, "country"]
Uma coluna (2d) DataFrame df.loc[:, ["country"]]
Várias colunas DataFrame df.loc[:, ["country", "age"]]
Faixa de colunas DataFrame df.loc[:, "name":"country"]
Uma linha (1d) Series df.loc[1000, :]
Uma linha (2d) DataFrame df.loc[[1000], :]
Várias linhas DataFrame df.loc[[1003, 1000], :]
Intervalo de linhas DataFrame df.loc[1000:1002, :]

Label Slicing Has Closed Intervals


Usar a notação de slice com rótulos é inconsistente em relação a tudo o mais em
Python e trabalhos de pandas: incluem a extremidade superior.

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.

Selecionando por posição


Selecionar um subconjunto de um DataFrame por posição corresponde ao que fizemos no
início deste capítulo com arrays NumPy. Com DataFrames, no entanto, você deve usar o
atributo iloc, que significa local inteiro:
df.iloc[row_selection, column_selection]

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!

Selecionando por indexação booleana


A indexação booleana refere-se à seleção de subconjuntos de um DataFrame com a ajuda de
um Series ou um DataFrame cujos dados consistem apenas em True ou False. Boolean
Series são usados para selecionar colunas e linhas específicas de um DataFrame, enquanto
Boolean DataFrames são usados para selecionar valores específicos em um DataFrame
inteiro. Mais comumente, você usará a indexação booleana para filtrar as linhas de um
DataFrame. Pense nisso como a funcionalidade AutoFiltro no Excel. Por exemplo, é assim
que você filtra seu DataFrame para que ele mostre apenas pessoas que moram nos EUA e
têm mais de 40 anos:
Página 98 Capítulo 5
In [24]: tf = (df["age"] > 40) & (df["country"] == "USA")
tf # This is a Series with only True/False
Out[24]: user_id
1001 False
1000 False
1002 True
1003 False
dtype: bool
In [25]: df.loc[tf, :]
Out[25]: properties name age country score continent
user_id
1002 Tim 41 USA 3.9 America

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.

Tabela 5-3. Operadores booleanos


Tipos de dados básicos do Python DataFrames e Series
and &
or |
not ~

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"

Se você deseja filtrar o índice, pode consultá-lo como df.index:


In [26]: df.loc[df.index > 1001, :]
Out[26]: properties name age country score continent
user_id
1002 Tim 41 USA 3.9 America
1003 Jenny 12 Germany 9.0 Europe

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.

Selecionando usando um MultiIndex


Um MultiIndex é um índice com mais de um nível. Ele permite que você agrupe
hierarquicamente seus dados e oferece acesso fácil a subconjuntos. Por exemplo, se você
definir o índice do nosso exemplo DataFrame df para uma combinação de continent e
country, você pode facilmente selecionar todas as linhas com um determinado continente:
In [31]: # A MultiIndex needs to be sorted
df_multi = df.reset_index().set_index(["continent", "country"])
Página 100 Capítulo 5
df_multi = df_multi.sort_index()
df_multi
Out[31]: properties user_id name age score
continent country
America USA 1000 John 33 6.7
USA 1002 Tim 41 3.9
Europe Germany 1003 Jenny 12 9.0
Italy 1001 Mark 55 4.5

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

Se você deseja redefinir seletivamente parte de um MultiIndex, forneça o nível como um


argumento. Zero é a primeira coluna da esquerda:
In [34]: df_multi.reset_index(level=0)
Out[34]: properties continent user_id name age score
country
USA America 1000 John 33 6.7
USA America 1002 Tim 41 3.9
Germany Europe 1003 Jenny 12 9.0
Italy Europe 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.

Configurando dados por indexação booleana


A indexação booleana, que usamos para filtrar linhas, também pode ser usada para atribuir
valores em um DataFrame. Imagine que você precise anonimizar todos os nomes de pessoas
com menos de 20 anos ou dos EUA:
In [38]: tf = (df2["age"] < 20) | (df2["country"] == "USA")
df2.loc[tf, "name"] = "xxx"
df2
Out[38]: properties name age country score continent
user_id
1001 Mark 55 Italy 4.0 Europe
1000 xxx 33 USA 3.0 America
Página 102 Capítulo 5
1002 xxx 41 USA 3.9 America
1003 xxx 12 Germany 9.0 Europe

À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.

Configurando dados substituindo valores


Se você deseja substituir um determinado valor em todo o DataFrame ou colunas
selecionadas, use o método replace:
In [41]: df2.replace("USA", "U.S.")
Out[41]: properties name age country score continent
user_id
1001 Mark 55 Italy 4.0 Europe
1000 xxx 33 U.S. 3.0 America
1002 xxx 41 U.S. 3.9 America
1003 xxx 12 Germany 9.0 Europe

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

Adicionar uma nova coluna geralmente envolve cálculos vetorizados:


In [43]: df2 = df.copy() # Let's start with a fresh copy
df2.loc[:, "birth year"] = 2021 - df2["age"]
df2
Out[43]: properties name age country score continent birth year
user_id
1001 Mark 55 Italy 4.5 Europe 1966
1000 John 33 USA 6.7 America 1988
1002 Tim 41 USA 3.9 America 1980
1003 Jenny 12 Germany 9.0 Europe 2009

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

No entanto, o verdadeiro poder dos pandas é seu mecanismo de alinhamento automático


de dados: quando você usa operadores aritméticos com mais de um DataFrame, os pandas
os alinham automaticamente por suas colunas e índices de linha. Vamos criar um segundo
DataFrame com alguns dos mesmos rótulos de linha e coluna. Construímos então a soma:
In [56]: more_rainfall = pd.DataFrame(data=[[100, 200], [300, 400]],
index=[1, 2],
columns=["City 1", "City 4"])
more_rainfall
Out[56]: City 1 City 4
1 100 200
2 300 400
In [57]: rainfall + more_rainfall
Out[57]: City 1 City 2 City 3 City 4
0 NaN NaN NaN NaN
1 200.2 NaN NaN NaN
2 NaN NaN NaN NaN

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

Isso funciona de acordo para os outros operadores aritméticos, conforme mostrado na


Tabela 5- 4.

Tabela 5-4. Operadores aritméticos


Operador Método
* mul
+ add
- sub
/ div
** pow

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

Ou, para localizar todos os nomes que começam com um “J”:


In [65]: users_cleaned.str.startswith("J")
Out[65]: 0 False
1 True
2 False
3 True
Name: name, dtype: bool

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.

Aplicando uma Função


DataFrames oferecem o método applymap, que aplicará uma função a cada elemento
individual, algo que é útil se não houver ufuncs NumPy disponíveis. Por exemplo, não há
ufuncs para formatação de string, então podemos formatar cada elemento de um
DataFrame assim:
Página 109 Capítulo 5
In [66]: rainfall
Out[66]: City 1 City 2 City 3
0 300.1 400.3 1000.5
1 100.2 300.4 1100.6
In [67]: def format_string(x):
return f"{x:,.2f}"
In [68]: # Note that we pass in the function without calling it,
# i.e., format_string and not format_string()!
rainfall.applymap(format_string)
Out[68]: City 1 City 2 City 3
0 300.10 400.30 1,000.50
1 100.20 300.40 1,100.60

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.

Ver vs. Copiar


Você deve se lembrar do capítulo anterior que fatiar arrays NumPy retorna uma visão. Com
DataFrames, infelizmente é mais complicado: nem sempre é fácil prever se loc e iloc
retornam visualizações ou cópias, o que o torna um dos tópicos mais confusos. Como é uma
grande diferença se você está alterando a exibição ou uma cópia de um DataFrame, o pandas
gera o seguinte aviso regularmente quando pensa que você está definindo os dados de
maneira não intencional: SettingWithCopyWarning. Para contornar esse aviso bastante
enigmático, aqui estão alguns conselhos:

• Defina valores no DataFrame original, não em um DataFrame que foi cortado de


outro DataFrame
• Se você quiser ter um DataFrame independente após o fatiamento, faça uma cópia
explícita:
selection = df.loc[:, ["country", "continent"]].copy()

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.

Figura 5-3. Tipos

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 e agregação de dados


Uma maneira de entender grandes conjuntos de dados é calcular uma estatística descritiva
como a soma ou a média em todo o conjunto de dados ou em subconjuntos significativos.
Esta seção começa examinando como isso funciona com pandas antes de introduzir duas
maneiras de agregar dados em subconjuntos: o método groupby e a função pivot_table.

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

Se você deseja a estatística por linha, forneça o argumento axis:


In [87]: rainfall.mean(axis=1)
Out[87]: 0 566.966667
1 500.400000
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

In [96]: data.plot() # Shortcut for data.plot.line()


Out[96]: <AxesSubplot:xlabel='Quarters'>
Página 119 Capítulo 5

Figura 5-4. Gráfico Matplotlib

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.

Mesmo se você usar o comando mágico %matplotlib notebook, provavelmente notará


que o Matplotlib foi originalmente projetado para gráficos estáticos e não para uma
experiência interativa em uma página da web. É por isso que vamos usar o Plotly a seguir,
uma biblioteca de plotagem projetada para a web.
Página 120 Capítulo 5
Plotly
Plotly é uma biblioteca baseada em JavaScript e pode - desde a versão 4.8.0 - ser usada como
um backend de plotagem de pandas com grande interatividade: você pode facilmente
aumentar o zoom, clicar na legenda para selecionar ou desmarcar uma categoria e obter dicas
de ferramentas com mais informações sobre o ponto de dados sobre o qual você está
passando o mouse. Plotly não está incluído na instalação do Anaconda, portanto, se você
ainda não o instalou, faça-o agora executando o seguinte comando:
(base)> conda install plotly

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()

Figura 5-5. Plotly line plot

In [99]: # Display the same data as bar plot


data.plot.bar(barmode="group")
Página 121 Capítulo 5

Figura 5-6. Plotly bar plot

Diferenças nos backends de plotagem


Se você usar o Plotly como backend de plotagem, precisará verificar os argumentos
aceitos dos métodos de plotagem diretamente nos documentos do Plotly. Por
exemplo, você pode dar uma olhada no argumento barmode=group na
documentação de gráficos de barras do Plotly.

pandas e as bibliotecas de plotagem subjacentes oferecem uma variedade de tipos de gráficos


e opções para formatar os gráficos de quase qualquer maneira desejada. Também é possível
organizar várias parcelas em uma série de subtramas. Como visão geral, Tabela 5-6 mostra
os tipos de plotagem disponíveis.

Tabela 5-6. tipos de plotagem de pandas


Tipo Descrição
line Gráfico de linhas, padrão durante a execução df.plot()
bar Gráfico de barras verticais
barh Gráfico de barras horizontais
hist histograma
box Gráfico de caixa
kde Density plot, também pode ser usado via densidade
area Gráfico de área
scatter Gráfico de dispersão
hexbin Gráficos de compartimentos hexagonais
pie Gráfico de pizza
Página 122 Capítulo 5
Além disso, o pandas oferece algumas ferramentas e técnicas de plotagem de nível superior
que são compostas por vários componentes individuais. Para detalhes, consulte o
visualização de pandas documentação.

Outras bibliotecas de plotagem


O cenário de visualização científica em Python é muito ativo e, além do Matplotlib e Plotly,
existem muitas outras opções de alta qualidade para escolher que podem ser a melhor opção
para determinados casos de uso:
Seaborn
Seaborn é construído em cima do Matplotlib. Ele melhora o estilo padrão e adiciona
gráficos adicionais, como mapas de calor, que geralmente simplificam seu trabalho:
você pode criar gráficos estatísticos avançados com apenas algumas linhas de código.
Bokeh
Bokeh é semelhante ao Plotly em tecnologia e funcionalidade: é baseado em JavaScript
e, portanto, também funciona muito bem para gráficos interativos em notebooks
Jupyter. Bokeh está incluído no Anaconda.
Altair
Altair é uma biblioteca para visualizações estatísticas baseadas no projeto Vega. O Altair
também é baseado em JavaScript e oferece alguma interatividade como zoom.
HoloViews
HoloViews é outro pacote baseado em JavaScript que se concentra em facilitar a análise
e a visualização de dados. Com algumas linhas de código, você pode obter gráficos
estatísticos complexos.

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!

Importando e exportando DataFrames


Até agora, construímos DataFrames do zero usando listas aninhadas, dicionários ou
matrizes NumPy. É importante conhecer essas técnicas, mas normalmente os dados já estão
disponíveis e você só precisa transformá-los em um DataFrame. Para fazer isso, o pandas
oferece várias funções de leitura. Mas mesmo que você precise acessar um sistema
proprietário para o qual o pandas não oferece um leitor embutido, geralmente você tem um
pacote Python para se conectar a esse sistema e, uma vez que tenha os dados, é fácil
transformá-lo em um Quadro de dados. No Excel, a importação de dados é o tipo de
trabalho que você normalmente faria com o Power Query.
Página 123 Capítulo 5
Depois de analisar e alterar seu conjunto de dados, você pode querer enviar os resultados de
volta para um banco de dados ou exportá-lo para um arquivo CSV ou – dado o título do
livro – apresentá-lo em uma pasta de trabalho do Excel para seu gerente. Para exportar
DataFrames de pandas, use um dos métodos de exportação que DataFrames oferece. Tabela
5-7 mostra uma visão geral dos métodos mais comuns de importação e exportação.

Tabela 5-7. Importando e exportando DataFrames


Formato/sistema Importação: função pandas (pd) Exportação: Método DataFrame (df)
Arquivos CSV pd.read_csv df.to_csv
JSON pd.read_json df.to_json
HTML pd.read_html df.to_html
Área pd.read_clipboard df. to_clipboard
Arquivos Excel pd.read_excel df.to_excel
dados SQL pd.read_sql df.to_sql

Veremos pd.read_sql e pd.to_sql no Capítulo 11, onde os usaremos como parte de um


estudo de caso. E como vou dedicar todo o Capítulo 7 ao tópico de leitura e gravação de
arquivos do Excel com pandas, vou me concentrar na importação e exportação de arquivos
CSV nesta seção. Vamos começar exportando um DataFrame existente!

Exportando arquivos CSV


Se você precisar passar um DataFrame para um colega que pode não usar Python ou pandas,
geralmente é uma boa ideia passá-lo na forma de um arquivo CSV: praticamente todos os
programas sabem como importá-los. Para exportar nosso exemplo de DataFrame df para
um arquivo CSV, use o método to_csv:
In [100]: df.to_csv("course_participants.csv")
Se você quiser armazenar o arquivo em um diretório diferente, forneça o caminho completo
como uma string bruta, por exemplo, r"C:\path\to\desired\location\msft.csv".

Usar strings brutas para caminhos de arquivo no Windows


Em strings, a barra invertida é usada para escapar de determinados caracteres. É por
isso que para trabalhar com caminhos de arquivo no Windows, você precisa usar
barras invertidas duplas (C:\\path\\to\\file.csv) ou prefixar a string com um
r para transformá-la em uma string bruta que interpreta os caracteres
literalmente. Isso não é um problema no macOS ou no Linux, pois eles usam barras
nos caminhos.

Ao fornecer apenas o nome do arquivo como eu faço, ele produzirá o arquivo


course_participants.csv no mesmo diretório do notebook com o seguinte conteúdo:
Página 124 Capítulo 5
user_id,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

Agora que você já sabe como usar o método df.to_csv, vamos ver como funciona a
importação de um arquivo CSV!

Importando arquivos CSV


Importar um arquivo CSV local é tão fácil quanto fornecer seu caminho para a função
read_csv. MSFT.csv é um arquivo CSV que baixei do Yahoo! Finance e contém os preços
históricos diários das ações da Microsoft — você o encontrará no repositório
complementar, na pasta csv:
In [101]: msft = pd.read_csv("csv/MSFT.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.

Tabela 5-8. Métodos e atributos de exploração de DataFrame


DataFrame (df) Descrição
Método/Atributo
df.info() Fornece o número de pontos de dados, tipo de índice, dtype e
uso de memória.
df.describe() Fornece estatísticas básicas, incluindo contagem, média, padrão,
mínimo, máximo e percentis.
df.head(n=5) Retorna as primeiras n linhas do DataFrame.
df.tail(n=5) Retorna as últimas n linhas do DataFrame.
df.dtypes Retorna o dtype de cada coluna.

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

Vamos agora retornar à série temporal de ações da Microsoft do último capítulo. Ao


observar mais de perto os tipos de dados das colunas, você notará que a coluna Date
Página 129 Capítulo 6
tem o tipo object, o que significa que pandas interpretou os timestamps como strings:
In [5]: msft = pd.read_csv("csv/MSFT.csv")
In [6]: 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

Há duas maneiras de corrigir isso e transformá-lo em um tipo de dados datetime. A


primeira é executar a função to_datetime nessa coluna. Certifique-se de atribuir a coluna
transformada de volta ao DataFrame original se quiser alterá-la na origem:
In [7]: msft.loc[:, "Date"] = pd.to_datetime(msft["Date"])
In [8]: msft.dtypes
Out[8]: Date datetime64[ns]
Open float64
High float64
Low float64
Close float64
Adj Close float64
Volume int64
dtype: object

A outra possibilidade é informar read_csv sobre as colunas que contêm timestamps


usando o argumento parse_dates. parse_dates espera uma lista de nomes de colunas
ou índices. Além disso, você quase sempre deseja transformar os carimbos de data e hora no
índice do DataFrame, pois isso permitirá filtrar os dados facilmente, como veremos em
breve. Para poupar uma chamada extra set_index , forneça a coluna que você gostaria de
usar como índice através do argumento index_col, novamente como nome da coluna ou
índice:
In [9]: msft = pd.read_csv("csv/MSFT.csv",
index_col="Date", parse_dates=["Date"])
In [10]: msft.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 8622 entries, 1986-03-13 to 2020-05-27
Data columns (total 6 columns):
Página 130 Capítulo 6
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Open 8622 non-null float64
1 High 8622 non-null float64
2 Low 8622 non-null float64
3 Close 8622 non-null float64
4 Adj Close 8622 non-null float64
5 Volume 8622 non-null int64
dtypes: float64(5), int64(1)
memory usage: 471.5 KB
Como as informações revelam, agora você está lidando com um DataFrame que possui
um DatetimeIndex. Se você precisar alterar outro tipo de dados (digamos que deseja que
Volume seja um float em vez de um int), você terá duas opções: forneça
dtype={"Volume": float} como argumento para a função read_csv ou aplique o
método astype da seguinte forma:
In [11]: msft.loc[:, "Volume"] = msft["Volume"].astype("float")
msft["Volume"].dtype
Out[11]: dtype('float64')

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()

E finalmente, se você precisar acessar apenas partes de um DatetimeIndex, como a parte de


data sem a hora, acesse o atributo date assim:
In [13]: msft.index.date
Out[13]: array([datetime.date(1986, 3, 13), datetime.date(1986, 3, 14),
datetime.date(1986, 3, 17), ..., datetime.date(2020, 5, 22),
datetime.date(2020, 5, 26), datetime.date(2020, 5, 27)],
dtype=object)
Em vez de date, você também pode usar partes de uma data como year, month, day, etc.
Para acessar a mesma funcionalidade em uma coluna regular com tipo de dados datetime,
você terá que usar o atributo dt, por exemplo, df["column_name"].dt.date.
Com um DatetimeIndex, vamos ver como podemos filtrar o DataFrame para
determinados períodos de tempo!

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()

Figura 6-1. Preço de fechamento ajustado para MSFT

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.

Trabalhando com fusos horários


A Microsoft está listada na bolsa de valores Nasdaq. O Nasdaq está em Nova York e os
mercados fecham às 16h. Para adicionar essas informações adicionais ao índice do
DataFrame, primeiro adicione a hora de fechamento à data via DateOffset, depois anexe o
Página 132 Capítulo 6
fuso horário correto aos timestamps via tz_localize. Como a hora de fechamento é
aplicável apenas ao preço de fechamento, vamos criar um novo DataFrame com ele:
In [16]: # Add the time information to the date
msft_close = msft.loc[:, ["Adj Close"]].copy()
msft_close.index = msft_close.index + pd.DateOffset(hours=16)
msft_close.head(2)
Out[16]: Adj Close
Date
1986-03-13 16:00:00 0.062205
1986-03-14 16:00:00 0.064427
In [17]: # Make the timestamps time-zone-aware
msft_close = msft_close.tz_localize("America/New_York")
msft_close.head(2)
Out[17]: Adj Close
Date
1986-03-13 16:00:00-05:00 0.062205
1986-03-14 16:00:00-05:00 0.064427

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.

Mudanças de deslocamento e porcentagem


Em finanças, os retornos logarítmicos das ações geralmente são normalmente distribuídos.
Por retornos de log, quero dizer o logaritmo natural da razão entre o preço atual e o anterior.
Para ter uma ideia da distribuição dos retornos diários de log, vamos traçar um histograma.
Primeiro, no entanto, precisamos calcular os retornos de log. No Excel, normalmente é feito
com uma fórmula que envolve células de duas linhas, conforme mostrado na Figura 6-2.

Figura 6-2. Calculando retornos de log no Excel

Logaritmos no Excel e Python


Excel usa LN para denotar o logaritmo natural e LOG para o logaritmo com base 10.
O módulo matemático do Python e NumPy, no entanto, usam log para o
logaritmo natural e log10 para o logaritmo com base 10.

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()

Figura 6-3. Gráfico de histograma

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

[9950 rows x 4 columns]


Página 136 Capítulo 6
Você viu o poder dos pandas concat? alinhou automaticamente as séries temporais
individuais ao longo das datas. É por isso que você recebe valores NaN para aquelas ações
que não vão tão longe quanto a Apple. E desde MSFT tem valores NaN nas datas mais
recentes, você deve ter adivinhado que eu baixei MSFT.csv dois dias antes dos outros.
Alinhar séries temporais por data é uma operação típica que é muito complicada com o
Excel e, portanto, também muito propensa a erros. A eliminação de todas as linhas que
contêm valores ausentes garantirá que todas as ações tenham a mesma quantidade de pontos
de dados:
In [27]: adj_close = adj_close.dropna()
adj_close.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 3970 entries, 2004-08-19 to 2020-05-27
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 AAPL 3970 non-null float64
1 AMZN 3970 non-null float64
2 GOOGL 3970 non-null float64
3 MSFT 3970 non-null float64
dtypes: float64(4)
memory usage: 155.1 KB

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

Figura 6-4. Séries temporais rebaseadas

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

In [31]: import plotly.express as px


In [32]: fig = px.imshow(returns.corr(),
x=adj_close.columns,
y=adj_close.columns,
color_continuous_scale=list(
reversed(px.colors.sequential.RdBu)),
zmin=-1, zmax=1)
fig.show()

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

Figura 6-5. Mapa de calor de correlação

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()

Figura 6-6.média móvel

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.

Limitações com pandas


Quando seus DataFrames começam a ficar maiores, é uma boa ideia saber o limite superior
do que um DataFrame pode conter. Ao contrário do Excel, onde você tem um limite rígido
de aproximadamente um milhão de linhas e 12.000 colunas por planilha, o pandas tem
apenas um limite flexível: todos os dados devem caber na memória disponível de sua
máquina. Se não for esse o caso, pode haver algumas correções fáceis: carregue apenas as
colunas do seu conjunto de dados que você precisa ou exclua resultados intermediários para
liberar memória. Se isso não ajudar, existem alguns projetos que parecerão familiares aos
usuários do pandas, mas trabalham com big data. Um dos projetos, Dask, funciona em cima
do NumPy e pandas e permite que você trabalhe com grandes conjuntos de dados
dividindo-os em vários DataFrames pandas e distribuindo a carga de trabalho em vários
núcleos de CPU ou máquinas. Outros projetos de big data que trabalham com algum tipo
de DataFrame são Modin, Koalas, Vaex, PySpark, cuDF, Ibise PyArrow. Abordaremos
brevemente Modin no próximo capítulo, mas fora isso, isso não é algo que vamos explorar
mais neste livro.
Página 141 Capítulo 6
Conclusão
A análise de séries temporais é a área em que sinto que o Excel mais ficou para trás, então,
depois de ler este capítulo, você provavelmente entenderá por que os pandas têm um sucesso
tão grande em finanças, um setor que depende muito de séries temporais. Vimos como é
fácil trabalhar com fusos horários, reamostrar séries temporais ou produzir matrizes de
correlação, funcionalidade que não tem suporte no Excel ou requer soluções alternativas
complicadas.
Saber usar pandas não significa que você tenha que se livrar do Excel, pois os dois mundos
podem funcionar muito bem juntos: pandas DataFrames são uma ótima maneira de
transferir dados de um mundo para outro, como veremos em a próxima parte, que trata da
leitura e gravação de arquivos do Excel de forma que contorne totalmente o aplicativo Excel.
Isso é muito útil, pois significa que você pode manipular arquivos do Excel com Python em
todos os sistemas operacionais suportados pelo Python, incluindo Linux. Para iniciar essa
jornada, o próximo capítulo mostrará como os pandas podem ser usados para automatizar
processos manuais tediosos, como a agregação de arquivos do Excel em relatórios de resumo.
Página 142

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.

Estudo de caso: Relatórios em Excel


Este estudo de caso é inspirado em alguns projetos de relatórios do mundo real nos quais
estive envolvido nos últimos anos. Mesmo que os projetos tenham ocorrido em setores
completamente diferentes - incluindo telecomunicações, marketing digital e finanças - eles
ainda eram notavelmente semelhantes: o ponto de partida geralmente é um diretório com
arquivos do Excel que precisam ser processados em um relatório do Excel - geralmente em
uma base mensal. , semanalmente ou diariamente. No repositório complementar, no
diretório sales_data, você encontrará arquivos Excel com transações de vendas fictícias para
uma operadora de telecomunicações que vende planos diferentes (Bronze, Silver, Gold) em
algumas lojas nos Estados Unidos. Para cada mês, há dois arquivos, um na subpasta novo
para novos contratos e outro na subpasta existente para clientes existentes. Como os
relatórios vêm de sistemas diferentes, eles vêm em formatos diferentes: os novos clientes são
entregues como arquivos xlsx, enquanto os clientes existentes chegam no formato antigo
xls. Cada um dos arquivos possui até 10.000 transações, e nosso objetivo é produzir um
relatório em Excel que mostre o total de vendas por loja e mês.
Página 143 Capítulo 7
Para começar, vamos dar uma olhada no arquivo January.xlsx da subpasta new na Figura 7-
1.

Figura 7-1. As primeiras linhas de janeiro.xlsx

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

A função read_excel com Python 3.9


Este é o mesmo aviso que em Capítulo 5: se você estiver executando
pd.read_excel com Python 3.9 ou superior, certifique-se de usar pelo menos
pandas 1.2 ou você receberá um erro ao ler os arquivos xlsx.

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.

Exemplo 7-1. sales_report_pandas.py


from pathlib import Path

import pandas as pd

# Directory of this file


this_dir = Path(__file__).resolve().parent

# Read in all Excel files from all subfolders of sales_data


parts = []
for path in (this_dir / "sales_data").rglob("*.xls*"):
print(f'Reading {path.name}')
part = pd.read_excel(path, index_col="transaction_id")
parts.append(part)

# Combine the DataFrames from each file into a single DataFrame


# pandas takes care of properly aligning the columns
df = pd.concat(parts)

# 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")

# Resample to end of month and assign an index name


summary = pivot.resample("M").sum()
summary.index.name = "Month"

# Write summary report to Excel file


summary.to_excel(this_dir / "sales_report_pandas.xlsx")

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.

A maneira mais fácil de ler recursivamente em todos os arquivos do Excel de dentro de


um determinado diretório é usar o método rglob do objeto de caminho. glob é a
abreviação de globbing, que se refere à expansão do nome do caminho usando curingas.
O ? curinga representa exatamente um caractere, enquanto * representa qualquer
número de caracteres (incluindo zero). O r em rglob significa globbing recursivo, ou
seja, ele procurará arquivos correspondentes em todos os subdiretórios - de acordo,
glob ignoraria os subdiretórios. Usar *.xls* como a expressão globbing garante que
os arquivos antigos e novos do Excel sejam encontrados, pois corresponde a .xls e
.xlsx. Geralmente, é uma boa ideia aprimorar um pouco a expressão assim:
[!~$]*.xls*. Isso ignora arquivos temporários do Excel (o nome do arquivo começa
com ~$). Para obter mais informações sobre como usar globbing em Python, consulte
a documentação do Python.
Execute o script, por exemplo, clicando no botão Executar arquivo no canto superior
direito do VS Code. O script levará um momento para ser concluído e, uma vez concluído,
a pasta de trabalho do Excel sales_report_pandas.xlsx aparecerá no mesmo diretório que
o script. O conteúdo de Sheet1 deve se parecer com a Figura 7-2. Esse é um resultado
bastante impressionante para apenas dez linhas de código - mesmo que você precise ajustar
a largura da primeira coluna para poder ver as datas!

Figura 7-2. sales_report_pandas.xlsx (como está, sem ajustar nenhuma largura de


coluna)
Página 146 Capítulo 7
Para casos simples como este, o pandas oferece uma solução muito fácil para trabalhar com
arquivos do Excel. No entanto, podemos fazer muito melhor - afinal, um título, alguma
formatação (incluindo largura de coluna e um número consistente de decimais) e um
gráfico não fariam mal. É exatamente disso que trataremos no próximo capítulo, usando
diretamente as bibliotecas de gravação que os pandas usam sob o capô. Antes de chegarmos
lá, no entanto, vamos dar uma olhada mais detalhada em como podemos ler e gravar
arquivos do Excel com pandas.

Lendo e gravando arquivos do Excel com


pandas
O estudo de caso estava usando read_excel e to_excel com seus argumentos padrão
para manter as coisas simples. Nesta seção, mostrarei os argumentos e opções mais usados
ao ler e gravar arquivos do Excel com pandas. Começaremos com a função read_excel e
a classe ExcelFile antes de examinar o método to_excel e a classe ExcelWriter. Ao
longo do caminho, também apresentarei a with .

A Função read_excel e a classe ExcelFile


O estudo de caso utilizou pastas de trabalho do Excel onde os dados estavam
convenientemente na célula A1 da primeira planilha. Na realidade, seus arquivos do Excel
provavelmente não estão tão bem organizados. Nesse caso, o pandas oferece parâmetros
para ajustar o processo de leitura. Para os próximos exemplos, usaremos o arquivo
stores.xlsx que você encontrará na pasta xl do repositório complementar. A primeira folha
é mostrada na Figura 7-3.

Figura 7-3. A primeira planilha de stores.xlsx

Usando os parâmetros sheet_name, skiprowse usecols, podemos informar aos pandas


sobre o intervalo de células que queremos ler. Como de costume, é uma boa ideia dar uma
olhada nos tipos de dados do retornou DataFrame executando o método info:
In [3]: df = pd.read_excel("xl/stores.xlsx",
sheet_name="2019", skiprows=1, usecols="B:F")
df
Página 147 Capítulo 7
Out[3]: Store Employees Manager Since Flagship
0 New York 10 Sarah 2018-07-20 False
1 San Francisco 12 Neriah 2019-11-02 MISSING
2 Chicago 4 Katelin 2020-01-31 NaN
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 [4]: 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
3 Since 5 non-null datetime64[ns]
4 Flagship 5 non-null object
dtypes: datetime64[ns](1), int64(1), object(3)
memory usage: 368.0+ bytes

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

Para manipular valores NaN, use uma combinação de na_values e keep_default_na. A


próxima amostra diz aos pandas para interpretar apenas células com a palavra MISSING
como NaN e nada mais:
In [10]: df = pd.read_excel("xl/stores.xlsx", sheet_name="2019",
skiprows=1, usecols="B,C,F", skipfooter=2,
na_values="MISSING", keep_default_na=False)
df
Out[10]: Store Employees Flagship
0 New York 10 False
1 San Francisco 12 NaN
2 Chicago 4
3 Boston 5 True

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")

Quando a execução do código deixa o corpo da instrução with , o arquivo é fechado


automaticamente, havendo ou não uma exceção acontecendo. Isso garante que os recursos
sejam devidamente limpos. Objetos que suportam a instrução with são chamados
gerenciadores de contexto; isso inclui os objetos ExcelFile e ExcelWriter neste
capítulo, bem como objetos de conexão de banco de dados que veremos no Capítulo 11.

Vamos ver a classe ExcelFile em ação:


In [13]: with pd.ExcelFile("xl/stores.xls") as f:
df1 = pd.read_excel(f, "2019", skiprows=1, usecols="B:F", nrows=2)
df2 = pd.read_excel(f, "2020", skiprows=1, usecols="B:F", nrows=2)

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

Reading Arquivos xlsb via pandas


Se você usa pandas com uma versão abaixo de 1.3, a leitura de arquivos xlsb requer
que você especifique explicitamente o mecanismo na função read_excel ou na
classe ExcelFile:
pd.read_excel("xl/stores.xlsb", engine="pyxlsb")
Isso requer que o pacote pyxlsb seja instalado, já que não faz parte do Anaconda —
chegaremos a isso, assim como aos outros motores no próximo capítulo.

Para resumir, a Tabela 7-1 mostra os parâmetros mais usados read_excel. Você
encontrará a lista completa nos documentos oficiais.

Tabela 7-1. Parâmetros selecionados para read_excel


Parâmetro Descrição
sheet_name Em vez de fornecer um nome de planilha, você também pode fornecer o índice
da planilha (base zero), por exemplo, sheet_name=0. Se você definir
sheet_name=None, os pandas lerão a pasta de trabalho inteira e retornarão um
dicionário na forma de {"sheetname": df}. Para ler uma seleção de planilhas,
forneça uma lista com os nomes ou índices das planilhas.
skiprows Isso permite que você pule o número indicado de linhas.
usecols Se o arquivo incluir os nomes dos cabeçalhos das colunas, forneça-os em uma
lista para selecionar as colunas, por exemplo, ["Store", "Employees"].
Alternativamente, também pode ser uma lista de índices de coluna, por
exemplo, [1, 2], ou uma string (não uma lista!) de nomes de coluna do Excel,
incluindo intervalos, por exemplo, "B:D,G". Você também pode fornecer uma
função: como exemplo, para incluir apenas as colunas que começam com
Manager, use: usecols=lambda x: x.startswith("Manager").
nrows Número de linhas que você deseja ler.
index_col Indica qual coluna deve ser o índice, aceita um nome de coluna ou um índice,
por exemplo, index_col=0. Se você fornecer uma lista com várias colunas, um
índice hierárquico será criado.
Página 151 Capítulo 7
Parâmetro Descrição
header Se você definir header=None, os cabeçalhos inteiros padrão serão
atribuídos, exceto se você fornecer os nomes desejados por meio do
parâmetro names. Se você fornecer uma lista de índices, serão criados
cabeçalhos de coluna hierárquica.
names Forneça os nomes desejados de suas colunas como lista.
na_values Pandas interpreta os seguintes valores de célula como NaN por padrão
(introduzi NaN no Capítulo 5): células vazias, #NA, NA, null, #N/A, N/A, NaN,
n/a, -NaN, 1. #IND, nan, #N/AN/A, -1.#QNAN, -nan, NULL, -1.#IND, <NA>,
1.#QNAN. Se você quiser adicionar um ou mais valores a essa lista, forneça-
os por meio de na_values.
keep_default_na Se você quiser ignorar os valores padrão que os pandas interpretam como
NaN, defina keep_default_na=False.
convert_float Excel armazena todos os números internamente como floats e, por padrão,
pandas transforma números sem decimais significativos em inteiros. Se
você quiser mudar esse comportamento, defina convert_float=False
(isso pode ser um pouco mais rápido).
converters Permite fornecer uma função por coluna para converter seus valores. Por
exemplo, para tornar o texto em uma determinada coluna em maiúscula,
use o seguinte: converters={"column_name": lambda x: x.upper()}

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!

O método to_excel e a classe ExcelWriter


A maneira mais fácil de escrever um arquivo Excel com pandas é usar o método to_excel de
um DataFrame. Ele permite que você especifique em qual célula de qual planilha você
deseja gravar o DataFrame. Você também pode decidir se deseja ou não incluir os cabeçalhos
de coluna e o índice do DataFrame e como tratar tipos de dados como np.nan e np.inf
que não têm uma representação equivalente no Excel. Vamos começar criando um
DataFrame com diferentes tipos de dados e usar seu método to_excel:
In [16]: import numpy as np
import datetime as dt
In [17]: data=[[dt.datetime(2020,1,1, 10, 13), 2.222, 1, True],
[dt.datetime(2020,1,2), np.nan, 2, False],
[dt.datetime(2020,1,2), np.inf, 3, True]]
df = pd.DataFrame(data=data,
columns=["Dates", "Floats", "Integers", "Booleans"])
df.index.name="index"
df
Out[17]: Dates Floats Integers Booleans
index
0 2020-01-01 10:13:00 2.222 1 True
1 2020-01-02 00:00:00 NaN 2 False
2 2020-01-02 00:00:00 inf 3 True
Página 152 Capítulo 7
In [18]: df.to_excel("written_with_pandas.xlsx", sheet_name="Output",
startrow=1, startcol=1, index=True, header=True,
na_rep="<NA>", inf_rep="<INF>")
Executando o comando to_excel irá criar o arquivo Excel como mostrado na Figura 7-4
(você precisará tornar a coluna C mais larga para ver as datas corretamente):

Figura 7-4. write_with_pandas.xlsx

Se você quiser escrever vários DataFrames na mesma planilha ou em planilhas diferentes,


você precisará usar a classe ExcelWriter. O exemplo a seguir grava o mesmo DataFrame
em dois locais diferentes em Sheet1 e mais uma vez em Sheet2:
In [19]: withpd.ExcelWriter("written_with_pandas2.xlsx") as writer:
df.to_excel(writer, sheet_name="Sheet1", startrow=1, startcol=1)
df.to_excel(writer, sheet_name="Sheet1", startrow=10, startcol=1)
df.to_excel(writer, sheet_name="Sheet2")

Como estamos usando a classe ExcelWriter como gerenciador de contexto, o arquivo é


gravado automaticamente no disco quando sai do gerenciador de contexto, ou seja, quando
o recuo é interrompido. Caso contrário, você terá que chamar writer.save()
explicitamente. Para obter um resumo dos parâmetros mais usados que o to_excel aceita,
dê uma olhada na Tabela 7-2. Você encontrará a lista completa de parâmetros nos
documentos oficiais.

Tabela 7-2. Parâmetros selecionados para to_excel


Parâmetro Descrição
sheet_name Nome da planilha na qual gravar.
startrow e startrow é a primeira linha em que o DataFrame será gravado e startcol é
startcol a primeira coluna. Isso usa indexação baseada em zero, portanto, se você
quiser gravar seu DataFrame na célula B3, use startrow=2 e startcol=1.
index e header Se você deseja ocultar o índice e/ou o cabeçalho, defina-os como
index=False e header=False, respectivamente.
na_rep e Por padrão, np.nan será convertido em uma célula vazia, enquanto np.inf,
inf_rep a representação do infinito de NumPy, será convertida na string inf. Fornecer
valores permite alterar esse comportamento.
freeze_panes Congela as primeiras linhas e colunas fornecendo uma tupla: por exemplo (2,
1) irá congelar as primeiras linhas e a primeira coluna.
Página 153 Capítulo 7
Como você pode ver, ler e escrever arquivos simples do Excel com pandas funciona bem.
Existem limitações, porém - vamos ver quais!

Limitações ao usar pandas com arquivos do


Excel
Usar a interface do pandas para ler e gravar arquivos do Excel funciona muito bem para
casos simples, mas há limites:

• Ao gravar DataFrames em arquivos, você não pode incluir um título ou um gráfico.


• Não há como alterar o formato padrão do cabeçalho e do índice no Excel.
• Ao ler arquivos, o pandas transforma automaticamente as células com erros como
#REF! ou #NUM! em NaN, impossibilitando a busca de erros específicos em suas
planilhas.
• Trabalhar com arquivos grandes do Excel pode exigir configurações extras que são mais
fáceis de controlar usando os pacotes de leitura e gravação diretamente, como veremos
no próximo capítulo.

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.

Os Pacotes Reader e Writer


O cenário leitor e gravador pode ser um pouco avassalador: veremos nada menos que seis
pacotes nesta seção, pois quase todo tipo de arquivo do Excel requer um pacote diferente.
O fato de cada pacote usar uma sintaxe diferente que muitas vezes se desvia
substancialmente do modelo de objeto original do Excel não facilita as coisas — falarei mais
sobre o modelo de objeto do Excel no próximo capítulo. Isso significa que você
provavelmente terá que procurar muitos comandos, mesmo se for um desenvolvedor VBA
experiente. Esta seção começa com uma visão geral de quando você precisa de qual pacote
antes de introduzir um módulo auxiliar que torna o trabalho com esses pacotes um pouco
mais fácil.
Página 155 Capítulo 8
Depois disso, ele apresenta cada um dos pacotes em um estilo de livro de receitas, onde você
pode pesquisar como funcionam os comandos mais usados.

Quando usar qual pacote


Esta seção apresenta os seis pacotes a seguir para ler, gravar e editar arquivos do Excel:

• 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:

Tabela 8-1. Quando usar qual pacote


Excel File Format Ler Escrever Editar
Xlsx OpenPyXL OpenPyXL, XlsxWriter OpenPyXL
Xlsm OpenPyXL OpenPyXL, XlsxWriter OpenPyXL
xltx, xltm OpenPyXL OpenPyXL OpenPyXL
xlsb pyxlsb - -
xls, xlt xlrd xlwt xlutils

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:

• OpenPyXL pode ler, escrever e editar enquanto XlsxWriter só pode escrever.


• O OpenPyXL facilita a produção de arquivos do Excel com macros VBA.
• XlsxWriter está melhor documentado.
• O XlsxWriter tende a ser mais rápido que o OpenPyXL, mas dependendo do
tamanho da pasta de trabalho que você está escrevendo, as diferenças podem não ser
significativas.
Página 156 Capítulo 8
Onde está xlwings?
Se você está se perguntando onde xlwings está na tabela Tabela 8-1, a resposta está
nenhum lugar ou em qualquer lugar, dependendo do seu caso de uso: diferente
de qualquer um dos pacotes neste capítulo, xlwings depende do aplicativo Excel,
que geralmente não está disponível , por exemplo, se você precisar executar seus
scripts no Linux. Se, por outro lado, você não tem problema em executar seus
scripts no Windows ou macOS onde você tem acesso a uma instalação do Excel,
xlwings pode realmente ser usado como uma alternativa a todos os pacotes deste
capítulo. Como a dependência do Excel é uma diferença tão fundamental entre o
xlwings e todos os outros pacotes do Excel, apresentarei o xlwings no próximo
capítulo, que inicia a Parte IV deste livro.

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.

Tabela 8-2. Conversão de tipo de dados


Representação em Excel Tipo de dados Python
Célula vazia None
Célula com formato de data datetime.datetime (exceto para pyxlsb)
Célula com boolean bool
Célula com erro str (a mensagem de erro)
String str
Float float ou int

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!

Lendo com OpenPyXL


O código de exemplo a seguir mostra como executar tarefas comuns ao usar o OpenPyXL
para ler arquivos do Excel. Para obter os valores das células, você precisa abrir a pasta de
trabalho com data_only=True. O padrão está em False, que retornaria as fórmulas das
células em vez disso:
In [1]: import pandas as pd
import openpyxl
import excel
import datetime as dt
In [2]: # Open the workbook to read cell values.
# The file is automatically closed again after loading the data.
book = openpyxl.load_workbook("xl/stores.xlsx", data_only=True)
In [3]: # Get a worksheet object by name or index (0-based)
sheet = book["2019"]
sheet = book.worksheets[0]
In [4]: # Get a list with all sheet names
book.sheetnames
Out[4]: ['2019', '2020', '2019-2020']
In [5]: # Loop through the sheet objects.
# Instead of "name", openpyxl uses "title".
for i in book.worksheets:
print(i.title)
2019
2020
2019-2020
In [6]: # Getting the dimensions,
# i.e., the used range of the sheet
sheet.max_row, sheet.max_column
Out[6]: (8, 6)
In [7]: # Read the value of a single cell
# using "A1" notation and using cell indices (1-based)
Página 159 Capítulo 8
sheet["B6"].value
sheet.cell(row=6, column=2).value
Out[7]: 'Boston'
In [8]: # Read in a range of cell values by using our excel module
data = excel.read(book["2019"], (2, 2), (8, 6))
data[:2] # Print the first two rows
Out[8]: [['Store', 'Employees', 'Manager', 'Since', 'Flagship'],
['New York', 10, 'Sarah', datetime.datetime(2018, 7, 20, 0, 0), False]]

Escrever com OpenPyXL


OpenPyXL cria o arquivo Excel na memória e grava o arquivo quando você chama o método
save. O código a seguir produz o arquivo como mostrado na Figura 8-1:
In [9]: import openpyxl
from openpyxl.drawing.image import Image
from openpyxl.chart import BarChart, Reference
from openpyxl.styles import Font, colors
from openpyxl.styles.borders import Border, Side
from openpyxl.styles.alignment import Alignment
from openpyxl.styles.fills import PatternFill
import excel
In [10]: # Instantiate a workbook
book = openpyxl.Workbook()

# Get the first sheet and give it a name


sheet = book.active
sheet.title = "Sheet1"

# Writing individual cells using A1 notation


# and cell indices (1-based)
sheet["A1"].value = "Hello 1"
sheet.cell(row=2, column=1, value="Hello 2")

# Formatting: fill color, alignment, border and font


font_format = Font(color="FF0000", bold=True)
thin = Side(border_style="thin", color="FF0000")
sheet["A3"].value = "Hello 3"
sheet["A3"].font = font_format
sheet["A3"].border = Border(top=thin, left=thin,
right=thin, bottom=thin)
sheet["A3"].alignment = Alignment(horizontal="center")
sheet["A3"].fill = PatternFill(fgColor="FFFF00", fill_type="solid")

# Number formatting (using Excel's formatting strings)


sheet["A4"].value = 3.3333
sheet["A4"].number_format = "0.00"

# Date formatting (using Excel's formatting strings)


sheet["A5"].value = dt.date(2016, 10, 13)
Página 160 Capítulo 8
sheet["A5"].number_format = "mm/dd/yy"

# Formula: you must use the English name of the formula


# with commas as delimiters
sheet["A6"].value = "=SUM(A4, 2)"

# Image
sheet.add_image(Image("images/python.png"), "C1")

# Two-dimensional list (we're using our excel module)


data = [[None, "North", "South"],
["Last Year", 2, 5],
["This Year", 3, 6]]
excel.write(sheet, data, "A10")

# 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")

# Saving the workbook creates the file on disk


book.save("openpyxl.xlsx")

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.

Figura 8-1. O arquivo escrito pelo OpenPyXL (openpyxl.xlsx)

Editando com o OpenPyXL


Não há nenhum pacote leitor/gravador que possa realmente editar arquivos do Excel: na
realidade, o OpenPyXL lê o arquivo com tudo o que entende, depois escreve o arquivo
novamente do zero - incluindo quaisquer alterações você faz no meio. Isso pode ser muito
poderoso para arquivos simples do Excel que contêm principalmente células formatadas
com dados e fórmulas, mas é limitado quando você tem gráficos e outros conteúdos mais
avançados em sua planilha, pois o OpenPyXL os altera ou os descarta completamente.
Página 162 Capítulo 8
Por exemplo, a partir da v3.0.5, o OpenPyXL retomará os gráficos e removerá seu título.
Aqui está um exemplo simples de edição:
In [12]: # Read the stores.xlsx file, change a cell
# and store it under a new location/name.
book = openpyxl.load_workbook("xl/stores.xlsx")
book["2019"]["A1"].value = "modified"
book.save("stores_edited.xlsx")
Se você deseja escrever um arquivo xlsm, o OpenPyXL precisa trabalhar com um arquivo
existente que você precisa carregar com o parâmetro keep_vba definido como True:
In [13]: book = openpyxl.load_workbook("xl/macro.xlsm", keep_vba=True)
book["Sheet1"]["A1"].value = "Click the button!"
book.save("macro_openpyxl.xlsm")
O botão no arquivo de exemplo está chamando uma macro que mostra uma caixa de
mensagem. O OpenPyXL cobre muito mais funcionalidades do que posso cobrir nesta
seção; portanto, é uma boa idéia dar uma olhada nos documentos oficiais. Também veremos
mais funcionalidades no final deste capítulo, quando retomarmos o estudo de caso do
capítulo anterior.

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")

# Add a sheet and give it a name


sheet = book.add_worksheet("Sheet1")

# Writing individual cells using A1 notation


# and cell indices (0-based)
sheet.write("A1", "Hello 1")
sheet.write(1, 0, "Hello 2")

# Formatting: fill color, alignment, border and font


formatting = book.add_format({"font_color": "#FF0000",
"bg_color": "#FFFF00",
"bold": True, "align": "center",
"border": 1, "border_color": "#FF0000"})
Página 163 Capítulo 8
sheet.write("A3", "Hello 3", formatting)

# Number formatting (using Excel's formatting strings)


number_format = book.add_format({"num_format": "0.00"})
sheet.write("A4", 3.3333, number_format)

# Date formatting (using Excel's formatting strings)


date_format = book.add_format({"num_format": "mm/dd/yy"})
sheet.write("A5", dt.date(2016, 10, 13), date_format)

# Formula: you must use the English name of the formula


# with commas as delimiters
sheet.write("A6", "=SUM(A4, 2)")

# Image
sheet.insert_image(0, 2, "images/python.png")

# Two-dimensional list (we're using our excel module)


data = [[None, "North", "South"],
["Last Year", 2, 5],
["This Year", 3, 6]]
excel.write(sheet, data, "A10")

# Chart: see the file "sales_report_xlsxwriter.py" in the


# companion repo to see how you can work with indices
# instead of cell addresses
chart = book.add_chart({"type": "column"})
chart.set_title({"name": "Sales per Region"})
chart.add_series({"name": "=Sheet1!A11",
"categories": "=Sheet1!B10:C10",
"values": "=Sheet1!B11:C11"})
chart.add_series({"name": "=Sheet1!A12",
"categories": "=Sheet1!B10:C10",
"values": "=Sheet1!B12:C12"})
chart.set_x_axis({"name": "Regions"})
chart.set_y_axis({"name": "Sales"})
sheet.insert_chart("A15", chart)

# Closing the workbook creates the file on disk


book.close()

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

Você lê planilhas e valores de células da seguinte forma:


In [17]: import pyxlsb
import excel
In [18]: # Loop through sheets. With pyxlsb, the workbook
# and sheet objects can be used as context managers.
# book.sheets returns a list of sheet names, not objects!
# To get a sheet object, use get_sheet() instead.
with pyxlsb.open_workbook("xl/stores.xlsb") as book:
for sheet_name in book.sheets:
withbook.get_sheet(sheet_name) as sheet:
dim = sheet.dimension
print(f"Sheet '{sheet_name}' has "
f"{dim.h} rows and {dim.w} cols")
Página 165 Capítulo 8
Sheet '2019' has 7 rows and 5 cols
Sheet '2020' has 7 rows and 5 cols
Sheet '2019-2020' has 20 rows and 5 cols
In [19]: # Read in the values of a range of cells by using our excel module.
# Instead of "2019", you could also use its index (1-based).
with pyxlsb.open_workbook("xl/stores.xlsb") as book:
withbook.get_sheet("2019") as sheet:
data = excel.read(sheet, "B2")
data[:2] # Print the first two rows
Out[19]: [['Store', 'Employees', 'Manager', 'Since', 'Flagship'],
['New York', 10.0, 'Sarah', 43301.0, False]]
pyxlsb atualmente não oferece nenhuma maneira de reconhecer células com datas, então
você terá que converter manualmente valores de células formatadas por data em objetos
datetime assim:
In [20]: from pyxlsb import convert_date
convert_date(data[1][3])
Out[20]: datetime.datetime(2018, 7, 20, 0, 0)

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")

xlrd, xlwt e xlutils


A combinação de xlrd, xlwt e xlutils oferece aproximadamente a mesma funcionalidade para
o formato legado xls que o OpenPyXL oferece para o formato xlsx: xlrd lê, xlwt escreve e
xlutils edita arquivos xls. Esses pacotes não são mais desenvolvidos ativamente, mas
provavelmente serão relevantes enquanto ainda houver arquivos em torno xls. xlutils não
faz parte do Anaconda, então instale-o se ainda não o fez:
(base)> conda install xlutils

Vamos começar com a parte de leitura!

Lendo com xlrd


O código de exemplo a seguir mostra como ler os valores de uma pasta de trabalho do Excel
com xlrd:
In [22]: import xlrd
import xlwt
from xlwt.Utils import cell_to_rowcol2
import xlutils
import excel
In [23]: # Open the workbook to read cell values. The file is
# automatically closed again after loading the data.
book = xlrd.open_workbook("xl/stores.xls")
Página 166 Capítulo 8
In [24]: # Get a list with all sheet names
book.sheet_names()
Out[24]: ['2019', '2020', '2019-2020']
In [25]: # Loop through the sheet objects
for sheet in book.sheets():
print(sheet.name)
2019
2020
2019-2020
In [26]: # Get a sheet object by name or index (0-based)
sheet = book.sheet_by_index(0)
sheet = book.sheet_by_name("2019")
In [27]: # Dimensions
sheet.nrows, sheet.ncols
Out[27]: (8, 6)
In [28]: # Read the value of a single cell
# using "A1" notation and using cell indices (0-based).
# The "*" unpacks the tuple that cell_to_rowcol2 returns
# into individual arguments.
sheet.cell(*cell_to_rowcol2("B3")).value
sheet.cell(2, 1).value
Out[28]: 'New York'
In [29]: # Read in a range of cell values by using our excel module
data = excel.read(sheet, "B2")
data[:2] # Print the first two rows
Out[29]: [['Store', 'Employees', 'Manager', 'Since', 'Flagship'],
['New York', 10.0, 'Sarah', datetime.datetime(2018, 7, 20, 0, 0),
False]]

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()

# Add a sheet and give it a name


sheet = book.add_sheet("Sheet1")

# Writing individual cells using A1 notation


# and cell indices (0-based)
sheet.write(*cell_to_rowcol2("A1"), "Hello 1")
sheet.write(r=1, c=0, label="Hello 2")

# Formatting: fill color, alignment, border and font


formatting = xlwt.easyxf("font: bold on, color red;"
"align: horiz center;"
"borders:top_color red, bottom_color red,"
"right_color red, left_color red,"
"left thin, right thin,"
"top thin, bottom thin;"
"pattern: pattern solid, fore_color yellow;")
sheet.write(r=2, c=0, label="Hello 3", style=formatting)

# Number formatting (using Excel's formatting strings)


number_format = xlwt.easyxf(num_format_str="0.00")
sheet.write(3, 0, 3.3333, number_format)

# Date formatting (using Excel's formatting strings)


date_format = xlwt.easyxf(num_format_str="mm/dd/yyyy")
sheet.write(4, 0, dt.datetime(2012, 2, 3), date_format)

# Formula: you must use the English name of the formula


# with commas as delimiters
sheet.write(5, 0, xlwt.Formula("SUM(A4, 2)"))

# Two-dimensional list (we're using our excel module)


data = [[None, "North", "South"],
["Last Year", 2, 5],
["This Year", 3, 6]]
excel.write(sheet, data, "A10")

# Picture (só permite adicionar formato bmp)


sheet.insert_bitmap("images/python.bmp", 0, 2)
Página 168 Capítulo 8
# Isso grava o arquivo no
book.save("xlwt.xls")

Editando com xlutils


xlutils atua como uma ponte entre xlrd e xlwt. Isso torna explícito que esta não é uma
operação de edição verdadeira: a planilha é lida incluindo a formatação via xlrd
(configurando formatting_info=True) e depois escrita novamente por xlwt, incluindo as
alterações que foram feitas no meio:
In [32]: import xlutils.copy
In [33]: book = xlrd.open_workbook("xl/stores.xls", formatting_info=True)
book = xlutils.copy.copy(book)
book.get_sheet(0).write(0, 0, "changed!")
book.save("stores_edited.xls")
Neste ponto, você sabe como ler e escrever uma pasta de trabalho do Excel em um formato
específico. A próxima seção segue com alguns tópicos avançados que incluem trabalhar com
arquivos grandes do Excel e usar pandas e os pacotes de leitor e gravador juntos.

Tópicos avançados do Reader e Writer


Se seus arquivos são maiores e mais complexos do que os arquivos simples do Excel que
usamos nos exemplos até agora, confiar nas opções padrão pode não ser mais suficiente.
Portanto, começamos esta seção analisando como trabalhar com arquivos maiores. Em
seguida, aprenderemos como usar pandas junto com os pacotes de leitor e gravador: isso
abrirá a capacidade de estilizar seus DataFrames de pandas da maneira que você quiser. Para
concluir esta seção, usaremos tudo o que aprendemos neste capítulo para tornar o relatório
do Excel do estudo de caso do capítulo anterior muito mais profissional.

Trabalhando com arquivos grandes do Excel


Trabalhar com arquivos grandes pode causar dois problemas: o processo de leitura e
gravação pode ser lento ou o computador pode ficar sem memória. Normalmente, o
problema de memória é uma preocupação maior, pois fará com que seu programa falhe.
Quando exatamente um arquivo é considerado grande, sempre depende dos recursos
disponíveis em seu sistema e sua definição de lento. Esta seção mostra as técnicas de
otimização oferecidas pelos pacotes individuais, permitindo que você trabalhe com
arquivos do Excel que ultrapassam os limites. Começarei examinando as opções para as
bibliotecas de gravadores, seguidas pelas opções para as bibliotecas de leitores. No final desta
seção, mostrarei como ler as planilhas de uma pasta de trabalho em paralelo para reduzir o
tempo de processamento.
Página 169 Capítulo 8
Escrevendo com OpenPyXL
Ao gravar arquivos grandes com OpenPyXL, certifique-se de ter o pacote lxml instalado,
pois isso torna o processo de gravação mais rápido. Ele está incluído no Anaconda, então
não há nada que você precise fazer sobre isso. A opção crítica, porém, é o sinalizador
write_only=True, que garante que o consumo de memória permaneça baixo. Isso, no
entanto, força você a escrever linha por linha usando o método append e não permitirá
mais que você escreva células únicas:
In [34]: book = openpyxl.Workbook(write_only=True)
# With write_only=True, book.active doesn't work
sheet = book.create_sheet()
# This will produce a sheet with 1000 x 200 cells
for row in range(1000):
sheet.append(list(range(200)))
book.save("openpyxl_optimized.xlsx")

Escrevendo com XlsxWriter


XlsxWriter tem uma opção semelhante como OpenPyXL chamada constant_memory. Isso
força você a escrever linhas sequenciais também. Você habilita a opção fornecendo um
dicionário options como esta:
In [35]: book = xlsxwriter.Workbook("xlsxwriter_optimized.xlsx",
options={"constant_memory": True})
sheet = book.add_worksheet()
# This will produce a sheet with 1000 x 200 cells
for row in range(1000):
sheet.write_row(row , 0, list(range(200)))
book.close()

Lendo com xlrd


Ao ler arquivos grandes no formato legado xls, xlrd permite carregar planilhas sob
demanda, assim:
In [36]: withxlrd.open_workbook("xl/stores.xls", on_demand=True) as book:
sheet = book.sheet_by_index(0) # Only loads the first sheet
Se você não usar a pasta de trabalho como gerenciador de contexto como fazemos aqui, você
precisaria chamar book.release_resources() manualmente para fechar a pasta de
trabalho corretamente novamente. Para usar xlrd neste modo com pandas, use-o assim:
In [37]: with xlrd.open_workbook("xl/stores.xls", on_demand=True) as book:
withpd.ExcelFile(book, engine="xlrd") as f:
df = pd.read_excel(f, sheet_name=0)
Página 170 Capítulo 8
Lendo com OpenPyXL
Para manter a memória sob controle ao ler arquivos grandes do Excel com OpenPyXL, você
deve carregar a pasta de trabalho com read_only=True. Como o OpenPyXL não suporta
a instrução with, você precisará fechar o arquivo novamente quando terminar. Se o seu
arquivo contém links para pastas de trabalho externas, você também pode usar
keep_links=False para torná-lo mais rápido. keep_links garante que as referências a
pastas de trabalho externas sejam mantidas, o que pode retardar desnecessariamente o
processo se você estiver interessado apenas em ler os valores de uma pasta de trabalho:
In [38]: book = openpyxl.load_workbook("xl/big.xlsx",
data_only=True, read_only=True,
keep_links=False)
# Perform the desired read operations here
book.close() # Required with read_only=True

Lendo planilhas em paralelo


Quando você usa a função read_excel para ler várias planilhas de uma grande pasta de
trabalho, você descobrirá que isso leva muito tempo (chegaremos a um exemplo concreto
em um momento). A razão é que os pandas lêem as folhas sequencialmente, ou seja, uma
após a outra. Para acelerar as coisas, você pode ler as folhas em paralelo. Embora não haja
uma maneira fácil de paralelizar a escrita de pastas de trabalho devido à forma como os
arquivos são estruturados internamente, ler várias planilhas em paralelo é bastante simples.
No entanto, como a paralelização é um tópico avançado, deixei-o de fora da introdução do
Python e também não entrarei em detalhes aqui.
Em Python, se você quiser aproveitar os vários núcleos de CPU que todo computador
moderno possui, use o pacote de multiprocessamento que faz parte da biblioteca padrão.
Isso gerará vários intérpretes Python (geralmente um por núcleo de CPU), que trabalham
em uma tarefa em paralelo. Em vez de processar uma planilha após a outra, você tem um
interpretador Python processando a primeira planilha, enquanto ao mesmo tempo um
segundo interpretador Python está processando a segunda planilha, etc. memória, portanto,
se você tiver arquivos pequenos, eles provavelmente ficarão mais lentos quando você
paralelizar o processo de leitura em vez de mais rápido. No caso de um arquivo grande com
várias folhas grandes, o multiprocessamento pode acelerar substancialmente o processo,
sempre assumindo que seu sistema tenha a memória necessária para lidar com a carga de
trabalho. Se você executar o notebook Jupyter no Binder, conforme mostrado no Capítulo
2, você não terá memória suficiente e, portanto, a versão paralelizada será executada mais
lentamente. No repositório complementar, você encontrará parallel_pandas.py, que é
uma implementação simples para ler as planilhas em paralelo, usando OpenPyXL como
mecanismo. É simples de usar, então você não precisa saber nada sobre multiprocessamento:
import parallel_pandas
parallel_pandas.read_excel(filename, sheet_name=None)
Página 171 Capítulo 8
Por padrão, ele lerá em todas as planilhas, mas você pode fornecer uma lista de nomes de
planilhas que deseja processar. Assim como os pandas, a função retorna um dicionário na
seguinte forma: {"sheetname": df}, ou seja, as chaves são os nomes das planilhas e os
valores são os DataFrames.

O Comando Mágico %%time


Nos exemplos a seguir, farei uso da mágica da célula %%time. Eu introduzi comandos
mágicos no Capítulo 5 em conexão com o Matplotlib. %%time é uma mágica de célula que
pode ser muito útil para ajustes de desempenho simples, pois facilita a comparação do tempo
de execução de duas células com trechos de código diferentes. Wall time é o tempo
decorrido do início ao fim do programa, ou seja, a célula. Se você estiver no macOS ou
Linux, você não apenas obterá o tempo de parede, mas uma linha adicional para os tempos
de CPU nestas linhas:
CPU times: user 49.4 s, sys: 108 ms, total: 49.5 s

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.

Lendo uma folha em paralelo com o Modin


Se você estiver lendo apenas uma folha enorme, vale a pena dar uma olhada no Modin, um
projeto que funciona como um substituto imediato para os pandas. Ele paraleliza o processo
de leitura de uma única folha e oferece melhorias de velocidade impressionantes. Como o
Modin requer uma versão específica do pandas, ele pode fazer o downgrade da versão que
vem com o Anaconda quando você o instala. Se você quiser testá-lo, eu recomendo que você
crie um ambiente separado Conda para isso para garantir que você não esteja atrapalhando
seu ambiente base. Consulte Apêndice A para obter instruções mais detalhadas sobre como
criar um ambiente Conda:
(base)> conda create --name modin python=3.8 -y
(base)> conda activate modin
(modin)> conda install -c conda-forge modin -y

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!

Formatando DataFrames no Excel


Para formatar DataFrames no Excel da maneira que queremos, podemos escrever código
que usa pandas junto com OpenPyXL ou XlsxWriter. Primeiro, usaremos essa combinação
para adicionar um título ao DataFrame exportado. Em seguida, formatamos o cabeçalho e
o índice de um DataFrame antes de encerrar esta seção formatando a parte de dados de um
DataFrame. Combinar pandas com OpenPyXL para leitura também pode ser útil
ocasionalmente, então vamos começar com isso:
In [41]: with pd.ExcelFile("xl/stores.xlsx", engine="openpyxl") as xlfile:
# Read a DataFrame
df = pd.read_excel(xlfile, sheet_name="2020")

# Get the OpenPyXL workbook object


book = xlfile.book

# From here on, it's OpenPyXL code


Página 173 Capítulo 8
sheet = book["2019"]
value = sheet["B3"].value # Read a single value

Ao escrever pastas de trabalho, funciona de forma análoga, permitindo adicionar facilmente


um título ao nosso relatório DataFrame:
In [42]: with pd.ExcelWriter("pandas_and_openpyxl.xlsx",
engine="openpyxl") as writer:
df = pd.DataFrame({"col1": [1, 2, 3, 4], "col2": [5, 6, 7, 8]})
# Write a DataFrame
df.to_excel(writer, "Sheet1", startrow=4, startcol=2)

# Get the OpenPyXL workbook and sheet objects


book = writer.book
sheet = writer.sheets["Sheet1"]

# From here on, it's OpenPyXL code


sheet["A1"].value = "This is a Title" # Write a single cell value

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.

Formatando o índice e os cabeçalhos de um DataFrame


A maneira mais fácil de obter controle total sobre a formatação do índice e dos cabeçalhos
de coluna é simplesmente escrevê-los você mesmo. O exemplo a seguir mostra como fazer
isso com OpenPyXL e XlsxWriter, respectivamente. Você pode ver a saída na Figura 8-2.
Vamos começar criando um DataFrame:
In [43]: df = pd.DataFrame({"col1": [1, -2], "col2": [-3, 4]},
index=["row1", "row2"])
df.index.name = "ix"
df
Out[43]: col1 col2
ix
row1 1 -3
row2 -2 4

Para formatar o índice e os cabeçalhos com OpenPyXL, faça o seguinte:


In [44]: from openpyxl.styles import PatternFill
In [45]: with pd.ExcelWriter("formatting_openpyxl.xlsx",
engine="openpyxl") as writer:
# Write out the df with the default formatting to A1
df.to_excel(writer, startrow=0, startcol=0)

# Write out the df with custom index/header formatting to A6


startrow, startcol = 0, 5
# 1. Write out the data part of the DataFrame
df.to_excel(writer, header=False, index=False,
Página 174 Capítulo 8
startrow=startrow + 1, startcol=startcol + 1)
# Get the sheet object and create a style object
sheet = writer.sheets["Sheet1"]
style = PatternFill(fgColor="D9D9D9", fill_type="solid")

# 2. Write out the styled column headers


for i, col in enumerate(df.columns):
sheet.cell(row=startrow + 1, column=i + startcol + 2,
value=col).fill = style

# 3. Write out the styled index


index = [df.index.name if df.index.name else None] + list(df.index)
for i, row in enumerate(index):
sheet.cell(row=i + startrow + 1, column=startcol + 1,
value=row).fill = style
Para formatar o índice e os cabeçalhos com XlsxWriter, você precisará ajustar um pouco o
código:
In [46]: # Formatting index/headers with XlsxWriter
with pd.ExcelWriter("formatting_xlsxwriter.xlsx",
engine="xlsxwriter") as writer:
# Write out the df with the default formatting to A1
df.to_excel(writer, startrow=0, startcol=0)

# Write out the df with custom index/header formatting to A6


startrow, startcol = 0, 5
# 1. Write out the data part of the DataFrame
df.to_excel(writer, header=False, index=False,
startrow=startrow + 1, startcol=startcol + 1)
# Get the book and sheet object and create a style object
book = writer.book
sheet = writer.sheets["Sheet1"]
style = book.add_format({"bg_color": "#D9D9D9"})

# 2. Write out the styled column headers


for i, col in enumerate(df.columns):
sheet.write(startrow, startcol + i + 1, col, style)

# 3. Write out the styled index


index = [df.index.name if df.index.name else None] + list(df.index)
for i, row in enumerate(index):
sheet.write(startrow + i, startcol, row, style)
Com o índice e o cabeçalho formatados, vamos ver como podemos estilizar a parte de
dados!

Figura 8-2. Um DataFrame com o formato padrão (esquerda) e com um formato


personalizado (direita)
Página 175 Capítulo 8
Formatando a parte de dados de um DataFrame
As possibilidades que você tem para formatar a parte de dados de um DataFrame dependem
do pacote que você está usando: se você usar o método dos pandas to_excel, o OpenPyXL
pode aplicar um formato a cada célula, enquanto o XlsxWriter só pode aplicar formatos em
uma base de linha ou coluna. Por exemplo, para definir o formato numérico das células para
três casas decimais e centralizar o conteúdo conforme mostrado na Figura 8-3, faça o
seguinte com OpenPyXL:
In [47]: from openpyxl.styles import Alignment
In [48]: with pd.ExcelWriter("data_format_openpyxl.xlsx",
engine="openpyxl") as writer:
# Write out the DataFrame
df.to_excel(writer)

# Get the book and sheet objects


book = writer.book
sheet = writer.sheets["Sheet1"]

# Formatting individual cells


nrows, ncols = df.shape
for row in range(nrows):
for col in range(ncols):
# +1 to account for the header/index
# +1 since OpenPyXL is 1-based
cell = sheet.cell(row=row + 2,
column=col + 2)
cell.number_format = "0.000"
cell.alignment = Alignment(horizontal="center")

Para XlsxWriter, ajuste o código da seguinte forma:


In [49]: with pd.ExcelWriter("data_format_xlsxwriter.xlsx",
engine="xlsxwriter") as writer:
# Write out the DataFrame
df.to_excel(writer)

# Get the book and sheet objects


book = writer.book
sheet = writer.sheets["Sheet1"]

# Formatting the columns (individual cells can't be formatted)


number_format = book.add_format({"num_format": "0.000",
"align": "center"})
sheet.set_column(first_col=1, last_col=2,
cell_format=number_format)
Página 176 Capítulo 8

Figura 8-3. Um DataFrame com uma parte de dados formatada

Como alternativa, pandas oferece suporte experimental para a propriedade style de


DataFrames. Experimental significa que a sintaxe pode mudar a qualquer momento. Como
os estilos foram introduzidos para formatar os DataFrames no formato HTML, eles usam
a sintaxe CSS. CSS significa cascading style sheets e é usado para definir o estilo dos
elementos HTML. Para aplicar o mesmo formato do exemplo anterior (três decimais e
alinhamento central), você precisará aplicar uma função a cada elemento de um objeto
Styler via applymap. Você obtém um objeto Styler através do atributo df.style:
In [50]: df.style.applymap(lambda x: "number-format: 0.000;"
"text-align: center")\
.to_excel("styled.xlsx")

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)

Figura 8-4. Um DataFrame com datas formatadas


Página 177 Capítulo 8
Outros pacotes Reader e Writer
Além dos pacotes que vimos neste capítulo, existem alguns outros que podem ser
interessantes para casos de uso específicos:
pyexcel
pyexcel oferece uma sintaxe harmonizada em diferentes pacotes do Excel e outros
formatos de arquivo, incluindo arquivos CSV e arquivos OpenOffice.
PyExcelerate
O objetivo do PyExcelerate é escrever arquivos do Excel da maneira mais rápida
possível.
pylightxl
pylightxl pode ler arquivos xlsx e xlsm e gravar arquivos xlsx.
styleframe
styleframe envolve pandas e OpenPyXL para produzir arquivos Excel com DataFrames
bem formatados.
oletools
oletools não é um pacote clássico de leitor ou gravador, mas pode ser usado para analisar
documentos do Microsoft Office, por exemplo, para análise de malware. Ele oferece
uma maneira conveniente de extrair código VBA de pastas de trabalho do Excel.

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!

Estudo de caso (revisitado): Relatório em Excel


Tendo chegado ao final deste capítulo, você já sabe o suficiente para poder voltar ao
relatório em Excel do estudo de caso do capítulo anterior e torná-lo visualmente mais
atraente. Se desejar, volte para sales_report_pandas.py no repositório complementar e
tente transformá-lo no relatório, conforme mostrado na Figura 8-5.
Os números vermelhos são números de vendas que estão abaixo de 20.000. Eu não toquei
em todos os aspectos da formatação neste capítulo (como aplicar a formatação condicional),
então você terá que usar a documentação do pacote com o qual você escolheu trabalhar.
Para comparar sua solução, incluí duas versões do script que produzem esse relatório no
repositório complementar. A primeira versão é baseada em OpenPyXL
(sales_report_openpyxl.py) e a outra é baseada em XlsxWriter
(sales_report_xlsxwriter.py). Ver os scripts lado a lado também pode permitir que você
tome uma decisão mais fundamentada sobre qual pacote deseja escolher para sua próxima
tarefa de escritor. Voltaremos a esse estudo de caso mais uma vez no próximo capítulo: lá,
contaremos com uma instalação do Microsoft Excel para trabalhar com modelos de
relatórios.
Página 178 Capítulo 8

Figura 8-5. O relatório de vendas revisto criado por sales_report_openpyxl.py

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.

Usando o Excel como visualizador de dados


Você provavelmente notou nos capítulos anteriores que, por padrão, os notebooks Jupyter
ocultam a maioria dos dados para DataFrames maiores e mostram apenas as linhas superior
e inferior, bem como as primeiras e últimas colunas. Uma maneira de ter uma ideia melhor
de seus dados é plotá-los – isso permite identificar discrepâncias ou outras irregularidades.
Às vezes, no entanto, é realmente útil poder rolar por uma tabela de dados. Depois de ler
Capítulo 7, você saberá como usar o método to_excel em seu DataFrame. Enquanto isso
funciona, pode ser um pouco complicado: você precisa dar um nome ao arquivo do Excel,
encontrá-lo no sistema de arquivos, abri-lo e, após fazer as alterações no seu DataFrame,
você precisa fechar o arquivo do Excel e executar o todo o processo novamente. Uma ideia
melhor pode ser executar df.to_clipboard(), que copia o DataFrame df para a área de
transferência, permitindo que você o cole no Excel, mas há uma maneira ainda mais simples
— use a função view que vem com xlwings:
In [1]: # Primeiro, vamos importar os pacotes que usaremos neste capítulo
import datetime as dt
import xlwings as xw
import pandas as pd
import numpy as np

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

[100 rows x 5 columns]


In [3]: # Visualize o DataFrame no Excel
xw.view(df)

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.

O modelo de objeto do Excel


Ao trabalhar com o Excel programaticamente, você interage com seus componentes como
uma pasta de trabalho ou uma planilha. Esses componentes são organizados no modelo de
objeto do Excel, uma estrutura hierárquica que representa a interface gráfica do usuário do
Excel (consulte a Figura 9-1). A Microsoft usa amplamente o mesmo modelo de objeto com
todas as linguagens de programação que suportam oficialmente, seja VBA, Office Scripts (a
interface JavaScript para Excel na Web) ou C#. Em contraste com os pacotes de leitor e
gravador do Capítulo 8, xlwings segue o modelo de objeto do Excel muito de perto, apenas
com uma lufada de ar fresco: por exemplo, xlwings usa os nomes app em vez de
application e book em vez de workbook:

• Um app contém ode coleção books


• Um book contém as planilhas de coleção sheets
• Uma sheet dá acesso a objetos range e coleções, como charts
• Um range contém uma ou mais células contíguas como seus itens

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)

Idioma e configurações regionais


Este livro é baseado na versão do Excel em inglês dos EUA. Ocasionalmente, me referirei a
nomes padrão como “Book1” ou “Sheet1”, que serão diferentes se você usar o Excel em
outro idioma. Por exemplo, “Sheet1” é chamado de “Feuille1” em francês e “Hoja1” em
espanhol. Além disso, o separador de lista, que é o separador que o Excel usa nas fórmulas
de células, depende de suas configurações: usarei a vírgula, mas sua versão pode exigir um
ponto e vírgula ou outro caractere. Por exemplo, em vez de escrever =SUM(A1, A2), você
precisará escrever =SUMME(A1; A2) em um computador com configurações regionais
alemãs.
No Windows, se você quiser alterar o separador de lista de ponto e vírgula para vírgula,
precisará alterá-lo fora do Excel por meio das configurações do Windows: clique no botão
Iniciar do Windows, procure por Configurações (ou clique no ícone de engrenagem) e vá
para "Hora e idioma"> "Região e idioma"> "Data, hora e configurações regionais adicionais",
onde você finalmente clica em "Região"> "Alterar local". Em "Separador de lista", você
poderá alterá-lo de ponto e vírgula para vírgula.

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

A indexação e o fatiamento funcionam com objetos xlwings range— observe o endereço


entre colchetes angulares (a representação do objeto impresso) para ver qual intervalo de
células você obtém:
In [11]: # Indexing
sheet1.range("A1:B2")[0, 0]
Out[11]: <Range [Book2]Sheet1!$A$1>
In [12]: # Slicing
sheet1.range("A1:B2")[:, 1]
Out[12]: <Range [Book2]Sheet1!$B$1:$B$2>

A indexação corresponde ao uso da Cells no VBA:


book.Sheets(1).Range("A1:B2").Cells(1, 1) # VBA
book.sheets[0].range("A1:B2")[0, 0] # xlwings

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

Equivale à seguinte expressão xlwings:


myrange = book.sheets[0].range((10, 4), (11, 6))

Índices baseados em zero vs. Um


Como um pacote Python, o xlwings usa consistentemente indexação baseada em
zero sempre que você acessa elementos por meio do índice ou sintaxe de fatia do
Python, ou seja, por meio de colchetes. Os objetos xlwings range, no entanto, use
os índices de linha e coluna baseados em um do Excel. Ter os mesmos índices de
linha/coluna que a interface de usuário do Excel às vezes pode ser benéfico. Se você
preferir usar apenas a indexação baseada em zero do Python, basta usar a sintaxe
sheet[row_selection, column_selection].
Página 189 Capítulo 9
O exemplo a seguir mostra como obter de um objeto range (sheet1["A1"]) todo o
caminho novamente para o objeto app. Lembre-se que o objeto app representa uma
instância do Excel (a saída entre colchetes representa o ID do processo do Excel e, portanto,
será diferente em sua máquina):
In [21]: sheet1["A1"].sheet.book.app
Out[21]: <Excel App 9092>

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."

In [28]: # Salve a pasta de trabalho do Excel no diretório xl


invisible_book.save("xl/invisible.xlsx")
In [29]: # Quit the invisible Excel instance
invisible_app.quit()
Página 190 Capítulo 9
macOS: acessando o sistema de arquivos programaticamente
Se você executar o comando save no macOS, receberá um pop-up Conceder
acesso ao arquivo no Excel que precisará confirmar clicando no botão Selecionar
antes de clicar em Conceder acesso. No macOS, o Excel está em área restrita, o
que significa que seu programa só pode acessar arquivos e pastas fora do aplicativo
Excel confirmando este prompt. Uma vez confirmado, o Excel lembrará os locais e
não o incomodará novamente quando você executar o script na próxima vez.

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.

Tabela 9-3. Trabalhando com a livros coleção


Comando Descrição
myapp.books.add() Cria uma nova pasta de trabalho do Excel na instância
do Excel à qual myapp refere-se e retorna o objeto
book
myapp.books.open(r"C:\path\Book.xlsx") Retorna o livro se já estiver aberto, caso contrário,
abre-o primeiro na instância do Excel à qual myapp se
refere. Lembre-se de que o r inicial transforma o
caminho do arquivo em uma string bruta para
interpretar as barras invertidas literalmente.
myapp.books["Book1.xlsx"] Retorna o objeto livro se estiver aberto. Isso vai
levantar um KeyError se ainda não estiver aberto.
Certifique-se de usar o nome e não o caminho
completo. Use isso se precisar saber se uma pasta de
trabalho já está aberta no Excel.

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.

Executando o código VBA


Se você tiver projetos herdados do Excel com muito código VBA, pode ser muito trabalhoso
migrar tudo para o Python. Nesse caso, você pode usar o Python para executar suas macros
VBA. O exemplo a seguir usa o arquivo vba.xlsm que você encontrará na pasta xl do
repositório complementar. Ele contém o seguinte código no Módulo1:
Function MySum(x As Double, y As Double) As Double
MySum = x + y
End Function
Página 191 Capítulo 9
Sub ShowMsgBox(msg As String)
MsgBox msg
End Sub

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")

In [31]: # Instanciar um objeto de macro com a função VBA


mysum = vba_book.macro("Module1.MySum")
# Call a VBA function
mysum(5, 4)
Out[31]: 9.0
In [32]: # Funciona da mesma forma com um procedimento VBA Sub
show_msgbox = vba_book.macro("Module1.ShowMsgBox")
show_msgbox("Hello xlwings!")
In [33]: # Feche o livro novamente (certifique-se de fechar o MessageBox
primeiro)
vba_book.close()

Não armazene funções VBA nos módulos Sheet e ThisWorkbook


Se você armazenar a função VBA MySum no módulo workbook This Workbook
ou um módulo de planilha (por exemplo, Sheet1), você deve se referir a ele como
ThisWorkbook.MySum ou Sheet1.MySum. No entanto, você não poderá acessar o
valor de retorno da função do Python, portanto, certifique-se de armazenar as
funções VBA em um módulo de código VBA padrão que você insere clicando com
o botão direito do mouse na pasta Módulos no editor VBA.

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.

Conversores, opções e coleções


Nos exemplos de código introdutórios deste capítulo, já estávamos lendo e escrevendo uma
string e uma lista aninhada de e para o Excel usando o atributo value de um objeto xlwings
range. Vou começar esta seção mostrando como isso funciona com pandas DataFrames
antes de dar uma olhada mais de perto no método options que nos permite influenciar
como o xlwings lê e grava valores. Continuamos com gráficos, imagens e nomes definidos,
as coleções que você costuma acessar a partir de um objeto sheet. Armados com esses
xlwings básicos, veremos novamente o estudo de caso de relatórios do Capítulo 7.
Página 192 Capítulo 9
Trabalhando com DataFrames
Escrever um DataFrame no Excel não é diferente de escrever um escalar ou uma lista
aninhada no Excel: basta atribuir o DataFrame à célula superior esquerda de um intervalo
do Excel:
In [34]: data=[["Mark", 55, "Italy", 4.5, "Europe"],
["John", 33, "USA", 6.7, "America"]]
df = pd.DataFrame(data=data,
columns=["name", "age", "country",
"score", "continent"],
index=[1001, 1000])
df.index.name = "user_id"
df
Out[34]: name age country score continent
user_id
1001 Mark 55 Italy 4.5 Europe
1000 John 33 USA 6.7 America
In [35]: sheet1["A6"].value = df

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.

In [37]: df2 = sheet1["A6"].expand().options(pd.DataFrame).value


df2
Out[37]: name age country score
continent user_id
1001.0 Mark 55.0 Italy 4.5 Europe
1000.0 John 33.0 USA 6.7 America
In [38]: # Se você deseja que o índice seja um índice inteiro,
# você pode alterar seu tipo de dados
Página 193 Capítulo 9
df2.index = df2.index.astype(int)
df2
Out[38]: name age country score
continent 1001 Mark 55.0 Italy 4.5
Europe
1000 John 33.0 USA 6.7 America
In [39]: # Ao definir index=False, ele colocará todos os valores do Excel em
# o parte de dados do DataFrame e usará o índice padrão
sheet1["A6"].expand().options(pd.DataFrame, index=False).value
Out[39]: user_id name age country score continent
0 1001.0 Mark 55.0 Italy 4.5 Europe
1 1000.0 John 33.0 USA 6.7 America

Ler e escrever DataFrames foi o primeiro exemplo de como conversores e opções


funcionam. Como eles são formalmente definidos e como você os usa com outras estruturas
de dados é o que veremos a seguir.

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.

Tabela 9-4. Conversores embutidos


Conversor Descrição
dict Dicionários simples sem aninhamento, ou seja, na forma {key1: value1,
key2: value2, ...}
np.array Arrays NumPy, requer import numpy como np
pd.Series pandas Series, requer import pandas as pd
pd.DataFrame pandas DataFrame, requer import pandas as pd

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]

In [48]: # Leia de volta usando as opções padrão


sheet1["A13:C13"].value
Out[48]: [datetime.datetime(2020, 1, 1, 0, 0), None, 1.0]

In [49]: # Leia de volta usando opções não padrão


sheet1["A13:C13"].options(empty="NA",
dates=dt.date,
numbers=int).value
Out[49]: [datetime.date(2020, 1, 1), 'NA', 1]

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, Figuras e Definidos Nomes


Nesta seção, mostrarei como trabalhar com três coleções que você acessa por meio do objeto
sheet ou book: gráficos, figuras e nomes definidos.4 xlwings suporta apenas a
funcionalidade de gráfico mais básica, mas como você pode trabalhar com modelos, pode
até não estar perdendo muito. E para compensar, o xlwings permite que você incorpore
gráficos do Matplotlib como imagens - você deve se lembrar do Capítulo 5 que o Matplotlib
é o backend de plotagem padrão dos pandas. Vamos começar criando um primeiro gráfico
do Excel!

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!

Figuras: Gráficos Matplotlib


Quando você usa o backend de plotagem padrão do pandas, você está criando um gráfico
Matplotlib. Para trazer esse gráfico para o Excel, primeiro você precisa obter seu objeto
figure, que você fornece como argumento para pictures.add — isso converterá o
gráfico em uma imagem e o enviará para o Excel:
In [52] : # Leia os dados do gráfico como DataFrame
df =
sheet1["A15"].expandir().opções(pd.DataFrame).value df
Out[52]: North South
Last Year 2.0
5.0
This Year 3.0 6.0
In [53]: # Habilite o Matplotlib usando o comando notebook magic
# e mude para o estilo "seaborn"
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use("seaborn")
In [54]: # O método pandas plot retorna um objeto "axis" de
# onde você pode obter a figura. "T" transpõe o
# DataFrame para trazer o gráfico para a orientação desejada
ax = df.T.plot.bar()
fig = ax.get_figure()
Em [55]: # Envia o gráfico para Excel
plot = sheet1.pictures.add(fig, name="SalesPlot",
top=sheet1["H19"].top,
left=sheet1["H19"].left)
# Vamos dimensionar o gráfico para 70%
plot.width, plot.height = plot.width * 0.7, plot.height * 0.7

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. Um gráfico do Excel (à esquerda) e um gráfico do Matplotlib (à direita)

Figura 9-2 mostra como o gráfico do Excel e o gráfico do Matplotlib se comparam após a
chamada update.

Certifique-se de que o Pillow está instalado


Ao trabalhar com imagens, certifique-se de que Pillow, a biblioteca de imagens do
Python, está instalada: isso garantirá que as imagens cheguem no tamanho e
proporção corretos no Excel. Pillow faz parte do Anaconda, portanto, se você usar
uma distribuição diferente, precisará instalá-la executando conda install
pillow ou pip install pillow. Observe que pictures.add também aceita
um caminho para uma imagem no disco em vez de uma figura Matplotlib.

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>]

In [62]: # Os nomes possuem vários métodos e atributos.


# Você pode, por exemplo, obter o respectivo objeto de intervalo.
book.names["matrix1"].refers_to_range
Out[62]: <Range [Book2]Sheet1!$A$1:$B$2>
In [63]: # Se você deseja atribuir um nome a uma constante
# ou uma fórmula, use o método "add"
book.names.add("EURUSD", "=1.1151")
Out[63]: <Name 'EURUSD': =1.1151>

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!

Estudo de caso (revisitado): Relatório do Excel


Ser capaz de editar verdadeiramente arquivos do Excel via xlwings nos permite trabalhar
com arquivos de modelo que serão 100% preservados, não importa quão complexos sejam
ou em que formato sejam armazenados - por exemplo, você pode editar facilmente um
arquivo xlsb, um caso que atualmente não é suportado por nenhum dos pacotes de
gravação que encontramos no capítulo anterior. Quando você olhar
sales_report_openpxyl.py no repositório complementar, você verá que depois de preparar
o DataFrame summary, tivemos que escrever quase quarenta linhas de código para criar um
gráfico e estilizar um DataFrame com OpenPyXL. Com xlwings, você consegue o mesmo
em apenas seis linhas de código, conforme mostrado no Exemplo 9-1. Ser capaz de lidar com
a formatação no modelo do Excel economizará muito trabalho. Isso, no entanto, tem um
preço: o xlwings requer uma instalação do Excel para ser executado - isso geralmente é bom
se você precisar criar esses relatórios com pouca frequência em sua própria máquina, mas
pode ser menos ideal se você tentar criar relatórios em um servidor como parte de uma
aplicação web.
Primeiro, você precisa ter certeza de que sua licença do Microsoft Office cobre a instalação
em um servidor e, segundo, o Excel não foi feito para automação autônoma, o que significa
que você pode ter problemas de estabilidade, especialmente se precisar gerar muitos
relatórios em um curto período de tempo. Dito isto, eu vi mais de um cliente fazendo isso
com sucesso, então se você não pode usar um pacote de gravação por qualquer motivo,
executar xlwings em um servidor pode muito bem ser uma opção que vale a pena explorar.
Apenas certifique-se de executar cada script em uma nova instância do Excel via app =
xw.App() para resolver os problemas típicos de estabilidade.
Página 200 Capítulo 9
Você encontrará o script completo xlwings em sales_report_xlwings.py no repositório
complementar (a primeira metade é a mesma que usamos com OpenPyXL e XlsxWriter).
Também é um exemplo perfeito para combinar um pacote de leitor com xlwings: enquanto
pandas (via OpenPyXL e xlrd) é mais rápido com a leitura de muitos arquivos do disco, o
xlwings facilita o preenchimento de um modelo pré-formatado.

Exemplo 9-1. sales_report_xlwings.py (somente segunda parte)


# Abra o modelo, cole os dados, ajuste automaticamente as colunas
# e ajuste a origem do gráfico. Em seguida, salve-o com um nome diferente.
template = xw.Book(this_dir / "xl" / "sales_report_template.xlsx") sheet =
template.sheets["Sheet1"]
sheet["B3"].value = summary
sheet["B3"].expand().columns.autofit()
sheet.charts["Chart 1"].set_source_data(sheet["B3"].expand()[:-1, :-1])
template.save(this_dir / "sales_report_xlwings.xlsx")

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

Figura 9-4. A tabela do relatório de vendas com base em um modelo pré-formatado

Tópicos avançados do xlwings


Esta seção mostra como melhorar o desempenho do código xlwings e como contornar a
funcionalidade ausente. Para entender esses tópicos, porém, primeiro precisamos dizer
algumas palavras sobre a maneira como o xlwings se comunica com o Excel.

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:

• Execute app.quit() do Python em vez de fechar o Excel manualmente. Isso garante


que as referências sejam limpas corretamente.
• Não elimine sessões interativas do Python quando você trabalha com xlwings, por
exemplo, se você executar um Python REPL em um prompt do Anaconda, desligue o
interpretador Python corretamente executando quit() ou usando o atalho Ctrl+Z.
Ao trabalhar com notebooks Jupyter, desligue o servidor clicando em Sair na interface
da web.
• Com sessões interativas do Python, ajuda a evitar o uso do objeto app diretamente, por
exemplo, usando xw.Book() ao invés de myapp.books.add(). Isso deve encerrar o
Excel corretamente, mesmo se o processo do Python for encerrado.
Página 203 Capítulo 9
Agora que você tem uma ideia sobre a tecnologia subjacente do xlwings, vamos ver como
podemos acelerar scripts lentos!

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!

Minimize as chamadas entre aplicativos


É crucial saber que cada chamada entre aplicativos do Python para o Excel é “cara”, ou seja,
lenta. Portanto, essas chamadas devem ser reduzidas o máximo possível. A maneira mais
fácil de fazer isso é lendo e escrevendo intervalos inteiros do Excel em vez de percorrer células
individuais. No exemplo a seguir, lemos e escrevemos 150 células, primeiro percorrendo
cada célula e depois lidando com todo o intervalo em uma chamada:
In [64]: # Adicione uma nova planilha e escreva 150 valores
# para ele ter algo para trabalhar
sheet2 = book.sheets.add()
sheet2["A1"].value = np.arange(150).reshape(30, 5)
In [65]: %%time
# Isso faz 150 chamadas entre aplicativos
for cell in sheet2["A1:E30"]:
cell.value += 1
Wall time: 909 ms
In [66]: %%time
# Isso faz apenas duas chamadas entre aplicativos
values = sheet2["A1:E30"].options(np.array).value
sheet2["A1"].value = values + 1
Wall time: 97.2 ms

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

No final do script, certifique-se de definir os atributos de volta ao seu estado original. Se


você estiver no Windows, também poderá ver uma ligeira melhoria de desempenho
executando seu script em uma instância oculta do Excel por meio de
xw.App(visible=False).
Agora que você sabe como manter o desempenho sob controle, vamos dar uma olhada em
como estender a funcionalidade do xlwings.

Como contornar a funcionalidade ausente


xlwings fornece uma interface Pythonic para os comandos do Excel mais usados e os faz
funcionar no Windows e no macOS. Existem, no entanto, muitos métodos e atributos do
modelo de objeto do Excel que ainda não são cobertos nativamente pelo xlwings — mas
nem tudo está perdido! xlwings fornece acesso ao objeto pywin32 subjacente no Windows
e ao objeto appscript no macOS usando o atributo api em qualquer objeto xlwings. Dessa
forma, você tem acesso a todo o modelo de objeto do Excel, mas, por sua vez, perde a
compatibilidade entre plataformas. Por exemplo, suponha que você queira limpar a
formatação de uma célula. Aqui está como você faria isso:

• 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

No último capítulo, aprendemos como escrever scripts em Python para automatizar o


Microsoft Excel. Embora isso seja muito poderoso, o usuário deve se sentir confortável
usando o Prompt do Anaconda ou um editor como o VS Code para executar os scripts. Esse
provavelmente não será o caso se suas ferramentas forem usadas por usuários corporativos.
Para eles, você desejará ocultar a parte do Python para que a ferramenta do Excel pareça uma
pasta de trabalho normal habilitada para macro novamente. Como você consegue isso com
xlwings é o tópico deste capítulo. Vou começar mostrando o caminho mais curto para
executar o código Python a partir do Excel antes de analisar os desafios de implantação das
ferramentas xlwings — isso também nos permitirá ter uma visão mais detalhada das
configurações disponíveis que o xlwings oferece. Como o último capítulo, este capítulo
requer que você tenha uma instalação do Microsoft Excel no Windows ou no macOS.

Usando Excel como Frontend com xlwings


O frontend é a parte de um aplicativo que um usuário vê e com a qual interage. Outros
nomes comuns para frontend são interface gráfica do usuário (GUI) ou apenas interface
do usuário (UI). Quando pergunto aos usuários do xlwings por que eles estão criando sua
ferramenta com o Excel em vez de construir um aplicativo web moderno, o que costumo
ouvir é: “Excel é a interface com a qual nossos usuários estão familiarizados”. Contar com
células de planilha permite que os usuários forneçam entradas de forma rápida e intuitiva,
tornando-os muitas vezes mais produtivos do que se eles tivessem que usar uma interface da
Web incompleta. Iniciarei esta seção apresentando o suplemento xlwings Excel e a CLI
xlwings (interface de linha de comando) antes de criar nosso primeiro projeto por meio do
comando quickstart. Vou encerrar esta seção mostrando duas maneiras de chamar
código Python do Excel: clicando no botão principal Executar no suplemento e usando a
função RunPython no VBA. Vamos começar instalando o add-in xlwings Excel!
Página 207 Capítulo 10
Suplemento do Excel
Como o xlwings está incluído na distribuição do Anaconda, no capítulo anterior,
poderíamos executar comandos xlwings em Python imediatamente. No entanto, se você
quiser chamar scripts Python do Excel, precisará instalar o suplemento do Excel ou
configurar a pasta de trabalho no modo autônomo. Embora eu apresente o modo
autônomo em “Implantação” na página 218, esta seção mostra como trabalhar com o
suplemento. Para instalá-lo, execute o seguinte em um Prompt do Anaconda:
(base)> xlwings addin install

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.

Figura 10-1. O suplemento da faixa de opções xlwings após executar o comando de


instalação

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.

Exemplo 10-1. first_project.py


import xlwings as xw

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()

xw.Book.caller() xlwings de objeto livro que se refere à pasta de trabalho do Excel


que está ativa quando você clica no botão Executar principal. No nosso caso,
corresponde a xw.Book("first_project.xlsm"). Usar xw.Book.caller() permite
renomear e mover seu arquivo Excel pelo sistema de arquivos sem quebrar a referência.
Ele também garante que você esteja manipulando a pasta de trabalho correta se ela
estiver aberta em várias instâncias do Excel.

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.

Explicarei as últimas três linhas quando examinarmos a depuração no próximo


capítulo. Para os propósitos deste capítulo, ignore ou mesmo exclua tudo abaixo da
primeira função.
O botão Executar principal no suplemento do Excel é um recurso de conveniência: ele
permite que você chame uma função com o nome main em um módulo Python que tenha
o mesmo nome do arquivo do Excel sem precisar adicionar um botão primeiro à sua pasta
de trabalho. Ele funcionará até mesmo se você salvar sua pasta de trabalho no formato sem
macro xlsx. Se, no entanto, você quiser chamar uma ou mais funções Python que não são
chamadas de main e não fazem parte de um módulo com o mesmo nome da pasta de
trabalho, você deve usar a função RunPython do VBA. A próxima seção tem os detalhes!

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.

Figura 10-2. O editor VBA mostrando Module1

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.

Figura 10-3. Desenhando um botão em uma planilha

Controles de formulário versus controles ActiveX


No Windows, você tem dois tipos de controles: controles de formulário e controles
ActiveX. Embora você possa usar um botão de qualquer grupo para se conectar à
sua macro SampleCall, apenas o dos controles de formulário também funcionará
no macOS. No próximo capítulo, usaremos Retângulos como botões para torná-
los um pouco mais modernos.

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.

Figura 10-4. A configuração PYTHONPATH

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!

RunPython sem comando


Se você deseja usar a função RunPython com uma pasta de trabalho existente que não foi
criada pelo comando quickstart, você precisa cuidar manualmente das coisas que o
comando quickstart faz para você caso contrário. Observe que as etapas a seguir são
necessárias apenas para a RunPython , mas não quando você deseja usar o botão principal
Executar:
Página 214 Capítulo 10
1. Primeiro, salve sua pasta de trabalho como uma pasta de trabalho habilitada para macro
com a extensão xlsm ou xlsb.
2. Adicione um módulo VBA; para fazer isso, abra o editor VBA via Alt+F11 (Windows)
ou Option-F11 (macOS) e certifique-se de selecionar o VBAProject da sua pasta de
trabalho na visualização em árvore no lado esquerdo, clique com o botão direito nele e
escolha Inserir > Módulo, conforme Figura 10-5. Isso inserirá um módulo VBA vazio
onde você pode escrever sua macro VBA com a chamada RunPython.

Figura 10-5. Adicionar um módulo VBA

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

Figura 10-6. RunPython requer uma referência ao xlwings

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.

Pastas de trabalho independentes: Livrando-se do


suplemento xlwings
Neste capítulo, sempre contamos com o suplemento xlwings para chamar o código Python
clicando no botão principal Executar ou usando a função RunPython. Mesmo que o CLI
xlwings facilite a instalação do add-in, ainda pode ser um incômodo para usuários menos
técnicos que não se sentem confortáveis usando o Anaconda Prompt. Além disso, como o
suplemento xlwings e o pacote xlwings Python precisam ter a mesma versão, você pode
encontrar um conflito em que seus destinatários já tenham o suplemento xlwings instalado,
mas com uma versão diferente da que sua ferramenta requer. No entanto, existe uma
solução simples: xlwings não requer o suplemento do Excel e pode ser configurado como
uma pasta de trabalho independente. Nesse caso, o código VBA do suplemento é
armazenado diretamente em sua pasta de trabalho. Como de costume, a maneira mais fácil
de configurar tudo é usando o comando quickstart, desta vez com o sinalizador --
standalone:
(base)> xlwings quickstart second_project --standalone

Ao abrir a pasta de trabalho gerada second_project.xlsm no Excel e pressionar Alt+F11


(Windows) ou Option-F11 (macOS), você verá o módulo xlwings e a classe Dictionary
que são necessários no lugar do suplemento. Mais importante ainda, um projeto autônomo
não deve mais ter uma referência ao xlwings. Embora isso seja configurado
automaticamente ao usar o sinalizador --standalone, é importante que você remova a
referência caso queira converter uma pasta de trabalho existente: vá para Ferramentas >
Referências em seu editor VBA e desmarque a caixa de seleção xlwings.
Página 217 Capítulo 10
Construindo um suplemento personalizado
Embora esta seção mostre como se livrar da dependência do suplemento xlwings,
às vezes você pode querer construir seu próprio suplemento para implantação. Isso
faz sentido se você quiser usar as mesmas macros com muitas pastas de trabalho
diferentes. Você encontrará instruções sobre como criar seu próprio suplemento
personalizado nos documentos xlwings.

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

• O código Python pode ser incorporado ao Excel, eliminando assim os arquivos de


origem externos.
• O pacote de relatórios permite que você transforme suas pastas de trabalho em modelos
com espaços reservados. Isso dá aos usuários não técnicos o poder de editar o modelo
sem precisar alterar o código Python.
• Os instaladores podem ser criados facilmente para se livrar de qualquer dor de cabeça
de implantação: os usuários finais podem instalar o Python, incluindo todas as
dependências com um único clique, dando a eles a sensação de lidar com pastas de
trabalho normais do Excel sem precisar configurar nada manualmente.

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!

O que vamos construir


Vá para o repositório complementar, onde você encontrará a pasta packagetracker. Existem
alguns arquivos nessa pasta, mas por enquanto apenas abra o arquivo do Excel
packagetracker.xlsm e vá para a planilha Database: primeiro precisamos colocar alguns
dados no banco de dados para ter algo com que trabalhar.
Página 221 Capítulo 11
Conforme mostrado na Figura 11-1, digite um nome de pacote, por exemplo “xlwings” e
clique em Add Package. Você pode escolher qualquer nome de pacote que exista no Python
Package Index (PyPI).

macOS: Confirme o acesso à pasta


Ao adicionar o primeiro pacote no macOS, você terá que confirmar um pop-up
para que o aplicativo possa acessar a pasta packagetracker. Este é o mesmo pop-up
que já encontramos no Capítulo 9.

Figura 11-1. O Python Package Tracker (folha de banco de dados)

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

Figura 11-2. O Python Package Tracker (folha Tracker)

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:

• JSON aceita apenas aspas duplas para strings


• JSON usa null onde o Python usa None
• JSON usa letras minúsculas true e false enquanto eles são capitalizados em
Python
• JSON aceita apenas strings como chaves, enquanto os dicionários Python aceitam
uma ampla variedade de objetos como chaves

O module json da biblioteca padrão permite converter um dicionário Python em uma


string JSON e vice-versa:
Página 224 Capítulo 11
In [1]: import json
In [2]: # A Python dictionary...
user_dict = {"name": "Jane Doe",
"age": 23,
"married": False,
"children": None,
"hobbies": ["hiking", "reading"]}
In [3]: # ...converted to a JSON string
# by json.dumps ("dump string"). The "indent" parameter is
# optional and prettifies the printing.
user_json = json.dumps(user_dict, indent=4)
print(user_json)
{
"name": "Jane Doe",
"age": 23,
"married": false,
"children": null,
"hobbies": [
"hiking",
"reading"
]
}
In [4]: # Converter a string JSON de volta para uma estrutura de dados nativa do
Python
json.loads(user_json)
Out[4]: {'name': 'Jane Doe',
'age': 23,
'married': False,
'children': None,
'hobbies': ['hiking', 'reading']}

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']

Observe que estamos escolhendo arbitrariamente o timestamp de lançamento do pacote


que aparece primeiro na lista. Uma versão específica geralmente tem vários pacotes para dar
conta de diferentes versões do Python e dos sistemas operacionais. Para encerrar este tópico,
você deve se lembrar do Capítulo 5 que pandas tem um método read_json para retornar
um DataFrame diretamente de uma string JSON. Isso, no entanto, não nos ajudaria aqui,
pois a resposta do PyPI não está em uma estrutura que possa ser transformada diretamente
em um DataFrame.
Esta foi uma breve introdução às APIs da Web para entender seu uso na base de código do
Python Package Tracker. Vamos agora ver como podemos nos comunicar com bancos de
dados, o outro sistema externo que usamos em nossa aplicação!

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.

O banco de dados Package Tracker


O banco de dados do Package Tracker Python não poderia ser mais simples, pois possui
apenas duas tabelas: a tabela packages armazena o nome do pacote e a tabela
package_versions armazena as strings de versão e a data do upload. As duas tabelas
podem ser unidas no package_id: em vez de armazenar o package_name com cada linha
na tabela package_versions, ele foi normalizado na tabela packages. Isso elimina dados
redundantes — as alterações de nome, por exemplo, só precisam ser feitas em um único
campo em todo o banco de dados. Para ter uma ideia melhor sobre a aparência do banco de
dados com os pacotes xlwings e pandas carregados, dê uma olhada nas Tabelas 11-1 e 11-2.

Tabela 11-1. A tabela de pacotes


package_id package_name
1 xlwings

2 pandas

Tabela 11-2. A tabela package_versions (três primeiras linhas de cada package_id)


package_id version_string uploaded_at
1 0.1.0 2014-03-19 18:18:49.000000
1 0.1.1 2014-06-27 16:26:36.000000
1 0.2.0 2014-07- 29 17:14:22.000000
... ... ...
2 0.1 2009-12-25 23:58:31.000000
2 0.2beta 2010-05-18 15:05:11.000000
2 0.2b1 2010-05-18 15: 09:05.000000
... ... ...

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

Figura 11-4. Acessando bancos de dados do Python

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.

Tabela 11-3. Drivers e strings de conexão padrão do SQLAlchemy


Databases driver padrão String de conexão
SQLite sqlite3 sqlite:///caminho
PostgreSQL psycopg2 postgresql://usuário:senha@host:porta/banco
MySQL mysql-python mysql://usuário:senha@host:porta/banco
Oracle cx_oracle oracle://usuário:senha@host:porta/banco
SQL Server pyodbc mssql+pyodbc://usuário:senha@host:porta/banco

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

Consultas de banco de dados vs. pandas DataFrames


SQL é uma linguagem set-based, o que significa que você opera em um conjunto
de linhas em vez de percorrer linhas individuais. Isso é muito semelhante a como
você trabalha com os DataFrames do pandas. A consulta SQL:
SELECT package_id, package_name FROM packages
corresponde à seguinte expressão de pandas (assumindo que packages é um
DataFrame):
packages.loc[:, ["package_id", "package_name"]]

Os exemplos de código a seguir usam o arquivo packagetracker.db que você encontrará na


pasta packagetracker do repositório complementar. Os exemplos esperam que você já tenha
adicionado xlwings e pandas ao banco de dados por meio do frontend Excel do Python
Package Tracker, como fizemos no início deste capítulo — caso contrário, você obteria
apenas resultados vazios. Seguindo Figura 11-4 de baixo para cima, primeiro faremos nossa
consulta SQL diretamente do driver, depois usaremos SQLAlchemy e finalmente pandas:
In [11]: # Vamos começar com as importações
import sqlite3
from sqlalchemy import create_engine
import pandas as pd
In [12]: # Nossa consulta SQL: "selecione todas as colunas da tabela de pacotes"
sql = "SELECT * FROM packages"

In [13]: # Opção 1: Driver de banco de dados (sqlite3 faz parte da biblioteca


padrão)
# Usando a conexão como gerenciador de contexto confirma automaticamente
# a transação ou a reverte em caso de erro.
withsqlite3.connect("packagetracker/packagetracker.db") as con:
cursor = con.cursor() # Precisamos de um cursor para executar
consultas SQL
result = cursor.execute(sql).fetchall() # Retornar todos os
registros
result
Out[13]: [(1, 'xlwings'), (2, 'pandas')]
Página 232 Capítulo 11
In [14]: # Option 2: SQLAlchemy
# "create_engine" expects the connection string of your database.
# Here, we can execute a query as a method of the connection object.
engine = create_engine("sqlite:///packagetracker/packagetracker.db")
with engine.connect() as con:
result = con.execute(sql).fetchall()
result
Out[14]: [(1, 'xlwings'), (2, 'pandas')]
In [15]: # Option 3: pandas
# Providing a table name to "read_sql" reads the full table.
# Pandas requires an SQLAlchemy engine that we reuse from
# the previous example.
df = pd.read_sql("packages", engine, index_col="package_id")
df
Out[15]: package_name
package_id
1 xlwings
2 pandas
In [16]: # "read_sql" also accepts an SQL query
pd.read_sql(sql, engine, index_col="package_id")
Out[16]: package_name
package_id
1 xlwings
2 pandas
In [17]: # The DataFrame method "to_sql" writes DataFrames to tables
# "if_exists" has to be either "fail", "append" or "replace"
# and defines what happens if the table already exists
df.to_sql("packages2", con=engine, if_exists="append")
In [18]: # The previous command created a new table "packages2" and
# inserted the records from the DataFrame df as we can
# verify by reading it back
pd.read_sql("packages2", engine, index_col="package_id")
Out[18]: package_name
package_id
1 xlwings
2 pandas
In [19]: # Let's get rid of the table again by running the
# "drop table" command via SQLAlchemy
withengine.connect() as con:
con.execute("DROP TABLE packages2")
Se você deve usar o driver de banco de dados, SQLAlchemy ou pandas para executar suas
consultas, depende muito de suas preferências: Eu pessoalmente gosto do controle refinado
que você obtém usando SQLAlchemy e gosto que eu possa usar o mesma sintaxe com
bancos de dados diferentes. Por outro lado, o read_sql é conveniente para obter o
resultado de uma consulta na forma de um DataFrame.
Página 233 Capítulo 11
Chaves estrangeiras com SQLite
Surpreendentemente, o SQLite não respeita chaves estrangeiras por padrão ao
executar consultas. No entanto, se você usar SQLAlchemy, poderá impor chaves
estrangeiras facilmente; veja os documentos do SQLAlchemy. Isso também
funcionará se você executar as consultas de pandas. Você encontrará o respectivo
código no topo do módulo database.py na pasta packagetracker do repositório
complementar.

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.

1 Na realidade, a ferramenta usa package_name em vez de package_id para simplificar o código.


Página 234 Capítulo 11
Portanto, sempre use a sintaxe que SQLAlchemy oferece para espaços reservados (eles
começam com dois pontos):
In [20]: # Let's start by importing SQLAlchemy's text function
from sqlalchemy.sql import text
In [21]: # ":package_id" is the placeholder
sql = """
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 = :package_id
ORDER BY v.uploaded_at
"""
In [22]: # Via SQLAlchemy
with engine.connect() as con:
result = con.execute(text(sql), package_id=1).fetchall()
result[:3] # Print the first 3 records
Out[22]: [('2014-03-19 18:18:49.000000', '0.1.0'),
('2014-06-27 16:26:36.000000', '0.1.1'),
('2014-07-29 17:14:22.000000', '0.2.0')]
In [23]: # Via pandas
pd.read_sql(text(sql), engine, parse_dates=["uploaded_at"],
params={"package_id": 1},
index_col=["uploaded_at"]).head(3)
Out[23]: version_string
uploaded_at
2014-03-19 18:18:49 0.1.0
2014-06-27 16:26:36 0.1.1
2014-07-29 17:14:22 0.2.0

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}")

ZeroDivisionError: division by zero

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:

Tabela 11-4. Separação de interesses


Camada Arquivo Descrição
Presentation packagetracker.xlsm Este é o frontend e, como tal, o único arquivo o
layer usuário final interage.
Business layer packagetracker.py Este módulo lida com o download de dados via API da
web e faz o processamento de números com pandas.
Data layer database.py Este módulo trata de todas as consultas do banco de
dados.
Database packagetracker.db Este é um arquivo de banco de dados SQLite.

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

Navegue até o diretório packagetracker e abra packagetracker.xlsm no Excel. Comece


adicionando as três guias, Tracker, Database e Dropdown, conforme mostrado na Figura
11-5.

Figura 11-5. Construindo a interface do usuário

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.

Tabela 11-5. Intervalos nomeados


Sheet Cell Name
Tracker B5 package_selection
Tracker B11 last_release
Database B5 new_package
Database B13 updated_at
Database B18 log

Uma maneira de adicionar intervalos nomeados é selecionar a célula, escrever o nome


na caixa Name e finalmente confirmar pressionando Enter, como na Figura 11-6.

Figura 11-6. A caixa de nome


Tabelas
Na planilha suspensa, depois de digitar “pacotes” na célula A1, selecione A1, vá para
Inserir > Tabela e certifique-se de ativar a caixa de seleção ao lado de “Minha tabela tem
cabeçalhos”. Para finalizar, com a tabela selecionada, vá até a aba Table Design
(Windows) ou Table (macOS) e renomeie a tabela de Table1 para
dropdown_content, conforme mostrado na Figura 11-7.
Página 240 Capítulo 11

Figura 11-7. Renomeando uma tabela do Excel

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.

Figura 11-8. Formatação condicional no Windows (esquerda) e macOS (direita)


Página 241 Capítulo 11
Gridlines
Nas planilhas Rastreador e Banco de dados, as linhas de grade foram ocultadas
desmarcando a caixa de seleção Exibir em Layout de página > Linhas de grade.
Neste ponto, a interface do usuário está completa e deve se parecer com a Figura 11-5. Agora
precisamos adicionar as chamadas RunPython no editor VBA e conectá-las com os botões.
Clique em Alt+F11 (Windows) ou Option-F11 (macOS) para abrir o editor VBA e, em
VBAProject de packagetracker.xlsm, clique duas vezes em Module1 no lado esquerdo em
Modules para abri-lo. Exclua o código existente SampleCall e substitua-o pelas seguintes
macros:
Sub AddPackage()
RunPython "import packagetracker; packagetracker.add_package()"
End Sub

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.

Figura 11-9. Atribua a macro ShowHistory ao botão Show History


Página 242 Capítulo 11
O frontend agora está pronto e podemos continuar com o backend Python.

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.

Sem código de produção


O aplicativo é mantido o mais simples possível para facilitar o acompanhamento
— ele não verifica todas as possíveis coisas que podem dar errado. Em um ambiente
de produção, você gostaria de torná-lo mais robusto: por exemplo, você mostraria
um erro amigável se não encontrar o arquivo de banco de dados.

Exemplo 11-1. A função add_package em packagetracker.py (sem comentários)

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)

O “erro” na mensagem de feedback acionará a fonte vermelha no Excel via formatação


condicional.

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.

A função store_package retorna None se a operação foi bem-sucedida e uma string


com a mensagem de erro caso contrário.

Para manter as coisas simples, todo o banco de dados é atualizado. Em um ambiente de


produção, você adicionaria apenas os registros do novo pacote.

Isso atualizará a tabela na planilha suspensa com o conteúdo da tabela packages.


Juntamente com a validação de dados que configuramos no Excel, isso garante que
todos os pacotes apareçam no menu suspenso da planilha Rastreador. Você precisaria
fornecer aos usuários uma maneira de chamar essa função diretamente se permitir que
o banco de dados seja preenchido de fora do seu arquivo Excel. Este é o caso assim que
você tiver vários usuários usando o mesmo banco de dados de arquivos diferentes do
Excel.
Você deve poder seguir as outras funções no arquivo packagetracker.py com a ajuda dos
comentários no código. Vamos agora voltar nossa atenção para o arquivo database.py. As
primeiras linhas são mostradas no Exemplo 11-2.

Exemplo 11-2. database.py (excerto com as importações relevantes)


from pathlib import Path

import sqlalchemy
import pandas as pd

...

# We want the database file to sit next to this file.


# Here, we are turning the path into an absolute path.
this_dir = Path(__file__).resolve().parent
db_path = this_dir / "packagetracker.db"

# 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.

Mudando para o PostgreSQL


Se você quiser substituir o SQLite pelo PostgreSQL, um banco de dados baseado em
servidor, há apenas algumas coisas que você precisa mudar. Antes de tudo, você precisa
executar o conda install psycopg2 (ou pip install psycopg2-binary se você não
estiver usando a distribuição Anaconda) para instalar o driver PostgreSQL. Em seguida, em
database.py, altere a string de conexão na função create_engine para a versão do
PostgreSQL conforme mostrado na Tabela 11-3. Finalmente, para criar as tabelas, você
precisaria alterar o tipo de dados INTEGER de packages.package_id à notação específica
do PostgreSQL de SERIAL. A criação de uma chave primária de incremento automático é
um exemplo de onde os dialetos SQL diferem.

Ao criar ferramentas da complexidade do Python Package Tracker, você provavelmente se


depara com alguns problemas ao longo do caminho: por exemplo, você pode ter renomeado
um intervalo nomeado no Excel e esquecido de ajustar o código Python adequadamente.
Este é um bom momento para analisar como a depuração funciona!

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.

Como esse código só é executado quando você executa o arquivo diretamente do


Python como um script, o comando set_mock_caller() serve apenas para fins de
depuração: quando você executa o arquivo no VS Code ou em um prompt do
Anaconda, ele define o xw.Book.caller() para
xw.Book("packagetracker.xlsm"). O único propósito de fazer isso é poder executar
seu script de ambos os lados, Python e Excel, sem ter que alternar o objeto book dentro
da função add_package entre xw.Book("packagetracker.xlsm") (quando você
chame-o do VS Code) e xw.Book.caller() (quando você o chama do Excel).
Página 246 Capítulo 11
Abra packagetracker.py no VS Code e defina um ponto de interrupção em qualquer linha
dentro da função add_package clicando à esquerda dos números de linha. Em seguida,
pressione F5 e selecione “Python File” na caixa de diálogo para iniciar o depurador e fazer
seu código parar no ponto de interrupção. Certifique-se de pressionar F5 em vez de usar o
botão Executar arquivo, pois o botão Executar arquivo ignora pontos de interrupção.

Depurando com VS Code e Anaconda


No Windows, quando você executa o depurador VS Code pela primeira vez com
código que usa pandas, você pode receber um erro: “Ocorreu uma exceção:
ImportError, Unable to import required dependencies: numpy.” Isso acontece
porque o depurador está funcionando antes que o ambiente Conda tenha sido
ativado corretamente. Como solução alternativa, para o depurador clicando no
ícone de parada e pressione F5 novamente - ele funcionará pela segunda vez.

Se você não estiver familiarizado com o funcionamento do depurador no VS Code, dê uma


olhada no Apêndice B , onde explico todas as funcionalidades e botões relevantes. Também
retomaremos o tópico na respectiva seção do próximo capítulo. Se você deseja depurar uma
função diferente, interrompa a sessão de depuração atual e ajuste o nome da função na parte
inferior do arquivo. Por exemplo, para depurar a função show_history, altere a última
linha em packagetracker.py da seguinte forma antes de pressionar F5 novamente:
if __name__ == " main ":
xw.Book("packagetracker.xlsm").set_mock_caller()
show_history()
No Windows, você também pode ativar a caixa de seleção Show Console no add-in xlwings,
que mostrará um prompt de comando enquanto a chamada RunPython estiver em
execução.2 Isso permite imprimir informações adicionais para ajudá-lo a depurar o
problema. Por exemplo, você pode imprimir o valor de uma variável para inspecioná-la no
prompt de comando. Após a execução do código, no entanto, o prompt de comando será
fechado. Se você precisar mantê-lo aberto por um pouco mais de tempo, há um truque fácil:
adicione input() como a última linha em sua função. Isso faz com que o Python aguarde
a entrada do usuário em vez de fechar o prompt de comando imediatamente. Quando
terminar de inspecionar a saída, pressione Enter no prompt de comando para fechá-lo -
apenas certifique-se de remover a input() novamente antes de desmarcar a opção Show
Console!

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

Uma observação para usuários de macOS e Linux


Mesmo se você não estiver no Windows, você ainda pode querer dar uma olhada
no estudo de caso do Google Trends, pois você pode adaptá-lo facilmente para
trabalhar com uma RunPython no macOS. Você também pode produzir um
relatório usando uma das bibliotecas de gravação do Capítulo 8, que funciona até
no Linux.

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.

Figura 12-1. Confie no acesso ao modelo de objeto do projeto VBA

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.

Figura 12-2. first_udf.xlsm

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.

Exemplo 12-1. first_udf.py (excerto)

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.

Por padrão, se os argumentos da função forem intervalos de células, xlwings fornecerá a


você os valores desses intervalos de células em vez do objeto xlwings range. Na grande
maioria dos casos, isso é muito conveniente e permite chamar a função hello com uma
célula como argumento. Por exemplo, você pode escrever “xlwings” na célula A2 e, em
seguida, alterar a fórmula em A1 para o seguinte:
=hello(A2)
O resultado será o mesmo da Figura 12-2. Mostrarei na última seção deste capítulo como
alterar esse comportamento e fazer com que os argumentos cheguem como objetos xlwings
range
Página 252 Capítulo 12
Em vez disso — como veremos a seguir, há ocasiões em que você precisará disso. No VBA,
a função equivalente hello ficaria assim:
Function hello(name As String) As String
hello = "Hello " & name & "!"
End Function

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.

Figura 12-3. A configuração dos módulos UDF

Clique no botão Import Functions para reimportar a função. Em seguida, recalcule a


fórmula no Excel para ter certeza de que tudo ainda funciona.

Importar funções de vários módulos Python


Se você deseja importar funções de vários módulos, use um ponto e vírgula entre
seus nomes na configuração de módulos UDF, por exemplo,
hello;another_module.

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.

Se você alterar o código Python de sua UDF, o xlwings coletará automaticamente as


alterações sempre que você salvar o arquivo Python. Como mencionado, você só precisa
reimportar suas UDFs se alterar algo no nome, argumentos ou decoradores da função. Se,
no entanto, seu arquivo de origem importar código de outros módulos e você alterar algo
nesses módulos, a maneira mais fácil de permitir que o Excel pegue todas as alterações é clicar
em Reiniciar servidor UDF.
Neste ponto, você sabe como escrever uma UDF simples em Python e como usá-la no Excel.
O estudo de caso na próxima seção apresentará UDFs mais realistas que fazem uso de
DataFrames pandas.

Estudo de caso: Google Trends


Neste estudo de caso, usaremos dados do Google Trends para aprender a trabalhar com
pandas DataFrames e matrizes dinâmicas, um dos novos recursos mais interessantes do
Excel que a Microsoft lançou oficialmente em 2020. Em seguida, criamos um UDF que se
conecta diretamente ao Google Trends, bem como uma que usa um gráfico de método do
DataFrame. Para encerrar esta seção, veremos como a depuração funciona com UDFs.
Vamos começar com uma breve introdução ao Google Trends!

Introdução ao Google Trends


Google Trends é um serviço do Google que permite analisar a popularidade das consultas
de pesquisa do Google ao longo do tempo e entre regiões. A Figura 12-4 mostra o Google
Trends após adicionar algumas linguagens de programação populares, selecionando
Worldwide como a região e 1/1/16 - 26/12/20 como o intervalo de tempo. Cada termo de
pesquisa foi selecionado com a linguagem de programação que aparece em uma lista
suspensa após digitar o termo de pesquisa. Isso garante que ignoremos Python, a cobra, e
Java, a ilha. O Google indexa os dados dentro do período e local selecionados com 100
representando o interesse de pesquisa máximo.
Página 255 Capítulo 12
Em nossa amostra, isso significa que, dentro do período e local especificados, o maior
interesse de pesquisa foi em Java em fevereiro de 2016. Para obter mais detalhes sobre o
Google Trends, dê uma olhada na postagem oficial do blog.

Figura 12-4. Juros ao longo do tempo; fonte de dados Trends

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!

Trabalhando com DataFrames e Arrays Dinâmicos


Tendo chegado tão longe no livro, você não deve se surpreender que os DataFrames dos
pandas também sejam os melhores amigos de um UDF. Para ver como DataFrames e UDFs
funcionam juntos e aprender sobre arrays dinâmicos, navegue até a pasta describe no
diretório udfs do repositório complementar e abra describe.xlsm no Excel e describe.py
no VS Code. O arquivo Excel contém os dados do Google Trends e no arquivo Python,
você encontrará uma função simples para começar, conforme mostrado no Exemplo 12-2.
Página 256 Capítulo 12
Exemplo 12-2. describe.py

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()

Em comparação com a função hello do projeto quickstart, você notará um segundo


decorador:
@xw.arg("df", pd.DataFrame, index=True, header=True)

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, ...)

Para ajudá-lo a fazer a conexão de volta ao Capítulo 9, o equivalente da função describe


na forma de um script se parece com isso (isso pressupõe que describe.xlsm está aberto no
Excel e que a função é aplicada ao intervalo A3:F263):
import xlwings as xw
import pandas as pd

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

Figura 12-5. A describe com matrizes dinâmicas

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:

• Certifique-se de que H3 é uma célula vazia selecionando-a e pressionando a tecla


Delete.
• Selecione o intervalo de saída começando na célula H3 e, em seguida, selecione todas as
células no caminho para M11.
• Com o intervalo H3:M11 selecionado, digite a fórmula =describe(A3:F263) e
confirme pressionando Ctrl+Shift+Enter.

Agora você deve ver quase a mesma imagem da Figura 12-5 com estas diferenças:

• Não há borda azul ao redor do intervalo H3:M11.


• A fórmula mostra chaves ao redor para marcá-la como uma matriz CSE:
{=describe(A3:F263)}.
• Enquanto você exclui matrizes dinâmicas indo para a célula superior esquerda e
pressionando a tecla Delete, com matrizes CSE, você sempre precisa selecionar a matriz
inteira primeiro para poder excluí-la.

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.

Adicione o parâmetro selection e torne-a opcional atribuindo None como seu


valor padrão.

Se selection for fornecida, filtre as colunas do DataFrame com base nela.


Depois de alterar a função, certifique-se de salvá-la e, em seguida, pressione o botão Import
Functions no add-in xlwings - isso é necessário, pois adicionamos um novo parâmetro.
Escreva Selection na célula A2 e TRUE nas células B2:F2. Por fim, ajuste sua fórmula na
célula H3 dependendo se você tem matrizes dinâmicas ou não:
Com matrizes dinâmicas
Selecione H3, altere a fórmula para =describe(A3:F263, B2:F2) e pressione Enter.
Sem matrizes dinâmicas
Começando na célula H3, selecione H3:M11 e pressione F2 para ativar o modo de
edição da célula H3 e alterar a fórmula para =describe(A3:F263, B2:F2). Para
finalizar, pressione Ctrl+Shift+Enter.
Para experimentar a função aprimorada, vamos alterar o TRUE na célula E2 para FALSE e
ver o que acontece: com arrays dinâmicos, você verá a tabela encolher magicamente em uma
coluna. No entanto, com arrays CSE legados, você acabará com uma coluna feia cheia de
valores #N/A, conforme mostrado na Figura 12-6.
Para contornar esse problema, o xlwings pode redimensionar matrizes CSE herdadas
usando o decorador de retorno. Adicione-o alterando sua função assim:
@xw.func
@xw.arg("df", pd.DataFrame)
@xw.ret(expand="table")
def describe(df, selection=None):
if selection is not None:
return df.loc[:, selection].describe()
else:
return df.describe()

Ao adicionar o decorador de retorno com a opção expand="table", xlwings


redimensionará o array CSE para corresponder às dimensões do DataFrame retornado.
Página 259 Capítulo 12

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.

Ordem dos Decoradores de Função


Certifique-se de colocar o decorador xw.func em cima dos xw.arg e xw.ret ;
observe que a ordem de xw.arg e xw.ret não importa.

O decorador de retorno funciona conceitualmente da mesma forma que o decorador de


argumento, com a única diferença de que você não precisa especificar o nome de um
argumento. Formalmente, sua sintaxe se parece com isso:
@xw.ret(convert=None, option1=value1, option2=value2, ...)

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().

Baixar um arquivo CSV e copiar/colar os valores em um arquivo do Excel funcionou bem


para este exemplo introdutório do DataFrame, mas copiar/colar é um processo tão
propenso a erros que você desejará se livrar dele sempre que puder. Com o Google Trends,
você pode sim, e a próxima seção mostra como!

Buscando dados do Google Trends


Os exemplos anteriores foram todos muito simples, praticamente apenas envolvendo um
pandas de função única. Para colocar as mãos em um caso mais real, vamos criar um UDF
que baixe os dados diretamente do Google Trends para que você não precise mais ficar
online e baixar um arquivo CSV manualmente. O Google Trends não possui uma API
oficial (interface de programação de aplicativos), mas existe um pacote Python chamado
pytrends que preenche a lacuna.
Página 261 Capítulo 12
Não ser uma API oficial significa que o Google pode alterá-la a qualquer momento,
portanto, existe o risco de que os exemplos desta seção parem de funcionar em algum
momento. No entanto, como o pytrends existe há mais de cinco anos no momento da
redação deste artigo, também há uma chance real de que ele seja atualizado para refletir as
alterações e fazê-lo funcionar novamente. De qualquer forma, serve como um bom exemplo
para mostrar que existe um pacote Python para praticamente qualquer coisa — uma
afirmação que fiz no Capítulo 1. Se você estivesse restrito a usar o Power Query,
provavelmente precisaria investir muito mais tempo para fazer algo funcionar - eu, pelo
menos, não consegui encontrar uma solução plug-and-play disponível gratuitamente.
Como o pytrends não faz parte do Anaconda e também não possui um pacote oficial
Conda, vamos instalá-lo com pip, se você ainda não fez isso:
(base)> pip install pytrends

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.

Exemplo 12-3. A função get_interest_over_time em google_trends.py (excerto


com as instruções de importação relevantes)

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()

# Make the Google Trends request and return the DataFrame


trend = TrendReq(timeout=10)
trend.build_payload(kw_list=mids,
timeframe=f"{start_date} {end_date}")
df = trend.interest_over_time()

# Replace Google's "mid" with a human-readable word


mids = {"/m/05z1_": "Python", "/m/02p97": "JavaScript",
"/m/0jgqg": "C++", "/m/07sbkfb": "Java", "/m/060kv": "PHP"}
df = df.rename(columns=mids)

# Drop the isPartial column


return df.drop(columns="isPartial")

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.

Opcionalmente, adicione uma docstring para o argumento da função, que será


mostrada no Function Wizard quando você editar o respectivo argumento, como na
Figura 12-8.

A docstring da função é exibida no Function Wizard, como na Figura 12-8.

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.

Estamos instanciando uma solicitação de objeto pytrends. Ao definir o timeout para


dez segundos, reduzimos o risco de ver um erro
requests.exceptions.ReadTimeout, que ocasionalmente acontece se o Google
Trends demorar um pouco mais para responder. Se você ainda vir esse erro, basta
executar a função novamente ou aumentar o tempo limite.
Página 263 Capítulo 12
Fornecemos os argumentos kw_list e timeframe para o objeto de solicitação.

Fazemos a solicitação real chamando interest_over_time, que retornará um


DataFrame pandas.

Renomeamos os mids com seu equivalente legível por humanos.

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.

Figura 12-7. google_trends.xlsm

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

Figura 12-8. O assistente de funções

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!

Plotando com UDFs


Como você deve se lembrar do Capítulo 5, chamando o método de um DataFrame plot
retorna um gráfico Matplotlib por padrão. Nos Capítulos 9 e 11, já vimos como você
adiciona um gráfico como uma imagem ao Excel.
Página 265 Capítulo 12
Ao trabalhar com UDFs, há uma maneira fácil de produzir gráficos: dê uma olhada na
segunda função em google_trends.py, mostrada no Exemplo 12-4.

Exemplo 12-4. A função plot em google_trends.py (excerto com as instruções de


importação relevantes)

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}>"

O argumento caller é um argumento especial reservado por xlwings: esse argumento


não será exposto quando você chamar a função de uma célula do Excel. Em vez disso,
caller será fornecido por xlwings nos bastidores e corresponde à célula da qual você
está chamando a função (na forma de um objeto xlwings range). Ter o range da célula
de chamada facilita a colocação do gráfico usando os argumentos top e left de
pictures.add. Argumento nome definirá o nome da imagem no Excel.

Definimos o estilo seaborn para tornar o enredo visualmente mais atraente.

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.

get_figure() retorna a figura do objeto Matplotlib de um gráfico DataFrame, que


é o que pictures.add espera.

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.

O argumento update=True garante que chamadas de função repetidas atualizarão a


imagem existente com o nome fornecido no Excel, sem alterar sua posição ou tamanho.
Página 266 Capítulo 12
Sem esse argumento, o xlwings reclamaria que já existe uma imagem com esse nome
no Excel.

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.

Figura 12-9. A plot em ação

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.

Vamos definir um ponto de interrupção na linha 29 clicando à esquerda do número da


linha. Se você não estiver familiarizado com o uso do depurador do VS Code, dê uma olhada
no Apêndice B , onde o apresento com mais detalhes. Quando você recalcular a célula A4,
sua chamada de função irá parar no ponto de interrupção e você poderá inspecionar as
variáveis. O que é sempre útil durante a depuração é executar df.info(). Ative a guia
Debug Console, escreva df.info() no prompt na parte inferior e confirme pressionando
Enter, conforme mostrado na Figura 12-10.

Depurando com VS Code e Anaconda


Este é o mesmo aviso do Capítulo 11: no Windows, quando você executa o
depurador VS Code pela primeira vez com código que usa pandas, você pode
receber um erro: “Ocorreu uma exceção: ImportError , Não é possível importar as
dependências necessárias: numpy.” Isso acontece porque o depurador está
funcionando antes que o ambiente Conda tenha sido ativado corretamente. Como
solução alternativa, pare o depurador clicando no ícone de parada e pressione F5
novamente - ele funcionará pela segunda vez.
Página 268 Capítulo 12

Figura 12-10. Usando o console de depuração enquanto o código está pausado em um


ponto 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.

Tópicos avançados de UDF


Se você usar muitas UDFs em sua pasta de trabalho, o desempenho pode se tornar um
problema. Esta seção começa mostrando as mesmas otimizações básicas de desempenho que
vimos no Capítulo 9, mas aplicadas a UDFs. A segunda parte trata do cache, uma técnica
adicional de otimização de desempenho que podemos usar com UDFs. Ao longo do
caminho, também aprenderemos como fazer com que os argumentos das funções cheguem
como objetos xlwings range em vez de valores.
Página 269 Capítulo 12
No final desta seção, apresentarei o decorador xw.sub que você pode usar como alternativa
à RunPython se estiver trabalhando exclusivamente no Windows.

Otimização básica de desempenho


Esta parte examina duas técnicas de otimização de desempenho: como minimizar chamadas
entre aplicativos e como usar o conversor de valores brutos.

Minimize as chamadas entre aplicativos


Como você provavelmente se lembra do Capítulo 9, as chamadas entre aplicativos, ou seja,
chamadas entre Excel e Python, são relativamente lentas, portanto, quanto menos UDFs
você tiver, melhor. Portanto, você deve trabalhar com arrays sempre que puder — ter uma
versão do Excel que suporte arrays dinâmicos definitivamente torna essa parte mais fácil.
Quando você está trabalhando com pandas DataFrames, não há muito que possa dar errado,
mas existem certas fórmulas em que você pode não pensar em usar matrizes
automaticamente. Considere o exemplo da Figura 12-11 que calcula as receitas totais como
a soma de uma determinada Taxa Base mais uma taxa variável determinada por Usuários
vezes Preço.

Figura 12-11. Fórmulas de célula única (esquerda) versus fórmulas baseadas em


matriz (direita)
Fórmulas de célula única
A tabela da esquerda na Figura 12-11 usa a fórmula =revenue($B$5, $A9, B$8) na
célula B9. Esta fórmula é então aplicada a todo o intervalo B9:E13. Isso significa que
você tem 20 fórmulas de célula única que chamam a função revenue.
Fórmulas baseadas em matriz
A tabela à direita na Figura 12-11 usa a fórmula =revenue2(H5, G9:G13, H8:K8). Se
você não tiver matrizes dinâmicas em sua versão do Excel, precisará adicionar o
decorador xw.ret(expand="table") à função revenue2 ou transformar a matriz
em uma matriz CSE herdada selecionando H9:K13, pressionando F2 para editar a
fórmula e confirmando com Ctrl+Shift+Enter. Ao contrário da fórmula de célula
única, esta versão só chama a função revenue2 uma vez.
Página 270 Capítulo 12
Você pode ver o código Python para as duas UDFs no Exemplo 12-5 e encontrará o arquivo
de origem na pasta revenues dentro do diretório udfs do repositório complementar.

Exemplo 12-5. receitas.py

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.

Usando valores brutos


Usar valores brutos significa que você está deixando de fora as etapas de preparação e
limpeza de dados que o xlwings faz em cima do pywin32, a dependência do xlwings no
Windows. Isso, por exemplo, significa que você não pode mais trabalhar com DataFrames
diretamente, pois o pywin32 não os entende, mas isso pode não ser um problema se você
trabalha com listas ou matrizes NumPy. Para usar UDFs com valores brutos, use a string
raw como o argumento convert no argumento ou decorador de retorno. Isso é o
equivalente a usar o raw pelo método options de um objeto xlwings range como fizemos
no Capítulo 9. De acordo com o que vimos na época, você obterá a maior velocidade
durante as operações de gravação. Por exemplo, chamar a seguinte função sem o decorador
de retorno seria cerca de três vezes mais lento no meu laptop:
import numpy as np
import xlwings as xw
Página 271 Capítulo 12
@xw.func
@xw.ret("raw")
def randn(i=1000, j=1000):
"""Returns an array with dimensions (i, j) with normally distributed
pseudorandom numbers provided by NumPy's random.randn
"""
return np.random.randn(i, j)

Você encontrará a respectiva amostra no repositório complementar na pasta raw_values


dentro do diretório udfs. Ao trabalhar com UDFs, você tem outra opção fácil para melhorar
o desempenho: evitar cálculos repetidos de funções lentas armazenando seus resultados em
cache.

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 = {}

def slow_sum(a, b):


key = (a, b)
if key in cache:
return cache[key]
else:
time.sleep(2) # sleep for 2 seconds
result = a + b
cache[key] = result
return result

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

Importe o decorador lru_cache.

Use o decorador. O decorador deve estar em cima do decorador xw.func.

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.

Cache com diferentes versões do Python


Se você estiver usando uma versão do Python abaixo de 3.8, terá que usar o
decorador com parênteses assim: @lru_cache(). Se você estiver usando Python 3.9
ou posterior, substitua @lru_cache por @cache, que é o mesmo que
@lru_cache(maxsize=None), ou seja, o cache nunca se livra de valores mais
antigos. Você também precisa importar o cache decorador de functools.

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

No Explorador de Arquivos, navegue até a pasta criada importsub e abra importsub.xlsm


no Excel e importsub.py no VS Code, então decore a função main com @xw.sub conforme
mostrado no Exemplo 12-6.
Página 274 Capítulo 12
Exemplo 12-6. importsub.py (excerto)
import xlwings as xw

@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.

Tabela 12-1. xlwings decorators


Decorador Descrição
xw.func Coloque este decorador em cima de todas as funções que você deseja importar para
o Excel como uma função do Excel.
xw.sub Coloque este decorador em cima de todas as funções que você deseja importar para
o Excel como um procedimento Sub do Excel.
xw.arg Aplique conversores e opções aos argumentos, por exemplo, adicione uma
docstring através do doc ou você pode fazer com que um intervalo chegue como
DataFrame fornecendo pd.DataFrame como o primeiro argumento (isso pressupõe
que você importou pandas como pd).
xw.ret Aplicar conversores e opções para retornar valores, por exemplo, suprimir o índice
de um DataFrame fornecendo index=False.

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:

• Agende a execução periódica de um script Python usando o Agendador de Tarefas no


Windows ou um cron job no macOS ou Linux. Você pode, por exemplo, criar um
relatório do Excel toda sexta-feira com base nos dados que você consome de uma API
REST ou de um banco de dados.
• Escreva um script Python que envie alertas por e-mail sempre que os valores em seus
arquivos do Excel atenderem a uma determinada condição. Talvez seja quando o saldo
da sua conta, consolidado de várias pastas de trabalho, cai abaixo de um determinado
valor ou quando mostra um valor diferente do que você espera com base em seu banco
de dados interno.
• Escreva o código que encontra erros nas pastas de trabalho do Excel: verifique se há
erros de célula como REF! ou #VALUE! ou erros lógicos como certificar-se de que uma
fórmula está incluindo todas as células que deveria. Se você começar a rastrear suas
pastas de trabalho de missão crítica com um sistema de controle de versão profissional
como o Git, poderá até mesmo executar esses testes automaticamente sempre que
confirmar uma nova versão.

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

No Capítulo 2, apresentei os ambientes Conda explicando que (base) no início de um


Prompt do Anaconda significa o ambiente Conda atualmente ativo com o nome base. O
Anaconda exige que você sempre trabalhe em um ambiente ativado, mas a ativação é feita
automaticamente para o ambiente base quando você inicia o Anaconda Prompt no
Windows ou o Terminal no macOS. Trabalhar com ambientes Conda permite separar
adequadamente as dependências de seus projetos: se você quiser experimentar uma versão
mais recente de um pacote como pandas sem alterar seu ambiente base, poderá fazê-lo em
um ambiente Conda separado. Na primeira parte deste apêndice, mostrarei o processo de
criação de um ambiente Conda chamado xl38, onde instalaremos todos os pacotes na
versão que usei para escrever este livro. Isso permitirá que você execute as amostras neste
livro como estão, mesmo que alguns pacotes tenham lançado novas versões com alterações
importantes nesse meio tempo. Na segunda parte, mostrarei como desabilitar a ativação
automática do ambiente base se você não gostar do comportamento padrão.

Crie um novo ambiente Conda


Execute o seguinte comando no prompt do Anaconda para criar um novo ambiente com o
nome xl38 que usa Python 3.8:
(base)> conda create --name xl38 python=3.8

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)>

Como usar o ambiente xl38


Se você gostaria de usar o xl38 em vez do ambiente base para trabalhar com os
exemplos deste livro, certifique-se de sempre ter seu xl38 ativado executando:
(base)> conda activate xl38
Ou seja, onde quer que eu mostre o Anaconda Prompt como (base)>, você irá
quer que ele mostre (xl38)> em vez disso.

Para desativar o ambiente novamente e voltar ao ambiente base, digite:


(xl38)> conda deactivate
(base)>

Se você deseja excluir o ambiente completamente, execute o seguinte comando:


(base)> conda env remove --name 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.

Desabilitar a ativação automática


Se você não quiser que o base seja ativado automaticamente sempre que você abrir um
Prompt do Anaconda, você pode desativá-lo: isso exigirá que você digite conda activate
base manualmente em um Prompt de Comando (Windows) ou Terminal (macOS ) antes
de poder usar o Python.
Windows
No Windows, você precisará usar o prompt de comando normal em vez do prompt do
Anaconda. As etapas a seguir habilitarão o comando conda em um prompt de
comando normal. Certifique-se de substituir o caminho na primeira linha pelo
caminho onde o Anaconda está instalado em seu sistema:
> cd C:\Users\username\Anaconda3\condabin
> conda init cmd.exe
Seu prompt de comando regular agora está configurado com Conda, então daqui para
frente você pode ativar o base assim:
> conda activate base
(base)>

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

Figura B-1. Código VS com o depurador parado no ponto de interrupção

Vamos ver o que cada um desses botões faz:


Continuar
Isso continua a executar o programa até atingir o próximo ponto de interrupção ou o
final do programa. Se chegar ao final do programa, o processo de depuração será
interrompido.
Step Over
O depurador avançará uma linha. Step Over significa que o depurador não percorrerá
visualmente as linhas de código que não fazem parte do seu escopo atual. Por exemplo,
ele não entrará no código de uma função que você chama linha por linha — mas a
função ainda será chamada!
Step Into
Se você tiver um código que chama uma função ou classe, etc., Step Into fará com que
o depurador entre nessa função ou classe. Se a função ou classe estiver em um arquivo
diferente, o depurador abrirá esse arquivo para você.
Página 282 Apêndice B
Step Out
Se você entrou em uma função com Step Into, Step Out faz com que o depurador
retorne ao próximo nível mais alto até que, eventualmente, você volte ao nível mais alto
de onde você chamou Step Into inicialmente.
Reiniciar
Isso interromperá o processo de depuração atual e iniciará um novo desde o início.
Parar
Isso interromperá o processo de depuração atual.
Agora que você sabe o que cada botão faz, clique em Step Over para avançar uma linha e
veja como a variável c aparece na seção Variables, então termine este exercício de depuração
clicando em Continue.
Se você salvar a configuração de depuração, o Painel de Comando não aparecerá e
perguntará sobre a configuração toda vez que você pressionar F5: clique no ícone Executar
na Barra de Atividades e clique em “criar um arquivo launch.json”. Isso fará com que o
Painel de Comando apareça novamente e quando você selecionar “Python File”, ele cria o
arquivo launch.json em um diretório chamado .vscode. Quando você pressionar F5, o
depurador será iniciado imediatamente. Se você precisar alterar a configuração ou quiser
obter novamente o pop-up do Painel de Comando, edite ou exclua o arquivo launch.json
no diretório .vscode.

Notebooks Jupyter no VS Code


Em vez de executar seus notebooks Jupyter em um navegador da Web, você também pode
executá-los diretamente com o VS Code. Além disso, o VS Code oferece um explorador de
variáveis conveniente, bem como opções para transformar o notebook em arquivos padrão
do Python sem perder a funcionalidade da célula. Isso torna mais fácil usar o depurador ou
copiar/colar células entre diferentes notebooks. Vamos começar executando um notebook
no VS Code!

Execute Jupyter Notebooks


Clique no ícone Explorer na Barra de atividades e abra ch05.ipynb no repositório
complementar. Para continuar, você precisará tornar o notebook confiável clicando em
Confiar na notificação que aparece. O layout do notebook parece um pouco diferente do
do navegador para combinar com o resto do VS Code, mas fora isso, é a mesma experiência
incluindo todos os atalhos de teclado. Vamos executar as três primeiras células via
Shift+Enter. Isso iniciará o servidor do notebook Jupyter se ele ainda não estiver em
execução (você verá o status no canto superior direito do notebook). Após executar as
células, clique no botão calculadora no menu na parte superior do notebook: isso abrirá o
explorador de variáveis, no qual você poderá ver os valores de todas as variáveis que existem
atualmente, como na Figura B-2. Ou seja, você só encontrará variáveis de células que foram
executadas.
Página 283 Apêndice B
Salvando notebooks Jupyter no VS Code
Para salvar notebooks no VS Code, você precisa usar o botão Salvar na parte
superior do notebook ou pressionar Ctrl+S no Windows ou Command-S no
macOS. Arquivo > Salvar não funcionará.

Figura B-2. Notebook Jupyter Explorador de variáveis

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.

Figura B-3. Visualizador de dados do notebook Jupyter

Embora o VS Code permita a execução de arquivos de notebook Jupyter padrão, ele


também permite transformar os notebooks em arquivos Python normais — sem perder suas
células. Vamos ver como isso funciona!
Página 284 Apêndice B
Scripts Python com células de código
Para usar células de notebook Jupyter em arquivos Python padrão, o VS Code usa um
comentário especial para denotar células: # %%. Para converter um notebook Jupyter
existente, abra-o e pressione o botão Export As no menu na parte superior do notebook;
veja a Figura B-2. Isso permitirá que você selecione “Python File” na paleta de comandos.
No entanto, em vez de converter um arquivo existente, vamos criar um novo arquivo que
chamamos cells.py com o seguinte conteúdo:
# %%
3 + 4
# %% [markdown]
# # Este é um título
#
# Some markdown content

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.

Figura B-4. Janela Interativa do Python

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

def accelerate(self, mph):


self.speed += mph
Esta é uma classe de carro simples com dois métodos. Métodos são funções que fazem parte
de uma definição de classe. Esta classe tem um método regular chamado accelerate. Este
método irá alterar os dados (speed) de uma instância desta classe. Ele também tem um
método especial que começa e termina com sublinhados duplos chamado init. Ele será
chamado automaticamente pelo Python quando um objeto for inicializado para anexar
alguns dados iniciais ao objeto.
Página 286 Apêndice C
O primeiro argumento de cada método representa a instância da classe e é chamado self
por convenção. Isso ficará mais claro quando você ver como você usa a classe Car. Primeiro,
vamos instanciar dois carros. Você está fazendo isso da mesma forma que está chamando
uma função: chame a classe adicionando parênteses e fornecendo os argumentos do método
init. Você nunca fornece nada para self, pois o Python cuidará disso. Neste exemplo,
self será car1 ou car2, respectivamente:
In [2]: # Vamos instanciar dois objetos car
car1 = Car("red")
car2 = Car(color="blue")

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().

Trabalhando com objetos datetime com


reconhecimento de fuso horário
No Capítulo 3, examinamos brevemente os objetos datetime. Se o fuso horário for
importante, você geralmente trabalha no UTC e só se transforma em fusos horários locais
para fins de exibição. UTC significa Coordinated Universal Time e é o sucessor do
Greenwich Mean Time (GMT). Ao trabalhar com Excel e Python, você pode querer
transformar carimbos de data/hora ingênuos, conforme fornecidos pelo Excel, em objetos
com reconhecimento de fuso horário datetime. Para suporte a fuso horário em Python,
você pode usar o pacote dateutil, que não faz parte da biblioteca padrão, mas vem pré-
instalado com o Anaconda. Os exemplos a seguir mostram algumas operações comuns ao
trabalhar com objetos datetime e fusos horários:
In [12]: import datetime as dt
from dateutil import tz
In [13]: # Time-zone-naive datetime object
timestamp = dt.datetime(2020, 1, 31, 14, 30)
timestamp.isoformat()
Out[13]: '2020-01-31T14:30:00'
In [14]: # Time-zone-aware datetime object
timestamp_eastern = dt.datetime(2020, 1, 31, 14, 30,
tzinfo=tz.gettz("US/Eastern"))
# Printing in isoformat makes it easy to
# see the offset from UTC
timestamp_eastern.isoformat()
Out[14]: '2020-01-31T14:30:00-05:00'
In [15]: # Assign a time zone to a naive datetime object
timestamp_eastern = timestamp.replace(tzinfo=tz.gettz("US/Eastern"))
timestamp_eastern.isoformat()
Out[15]: '2020-01-31T14:30:00-05:00'
In [16]: # Convert from one time zone to another.
# Since the UTC time zone is so common,
# there is a shortcut: tz.UTC
timestamp_utc = timestamp_eastern.astimezone(tz.UTC)
timestamp_utc.isoformat()
Out[16]: '2020-01-31T19:30:00+00:00'
In [17]: # From time-zone-aware to naive
timestamp_eastern.replace(tzinfo=None)
Página 288 Apêndice C
Out[17]: datetime.datetime(2020, 1, 31, 14, 30)
In [18]: # Current time without time zone
dt.datetime.now()
Out[18]: datetime.datetime(2021, 1, 3, 11, 18, 37, 172170)
In [19]: # Current time in UTC time zone
dt.datetime.now(tz.UTC)
Out[19]: datetime.datetime(2021, 1, 3, 10, 18, 37, 176299, tzinfo=tzutc())

Fusos horários com Python 3.9


Python 3.9 adicionou suporte de fuso horário adequado ao padrão biblioteca na forma do
módulo timezone. Use-o para substituir as tz.gettz chamadas dateutil:
from zoneinfo import ZoneInfo
timestamp_eastern = dt.datetime(2020, 1, 31, 14, 30,
tzinfo=ZoneInfo("US/Eastern"))

Objetos Python mutáveis vs. imutáveis


Em Python, objetos que podem mudar seus valores são chamados de mutáveis e aqueles que
não podem são chamados de imutáveis. A Tabela C-1 mostra como os diferentes tipos de
dados se qualificam.

Tabela C-1. Tipos de dados mutáveis e imutáveis


Mutabilidade Tipos de dados
inteiros listas mutáveis, dicionários, conjuntos
Saber imutáveis, floats, booleanos, strings, datetime, tuplas

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)

Isso imprime o seguinte:


1, 22, 3
1, 2, 3
Página 289 Apêndice C
Agora vamos fazer o mesmo exemplo em Python com uma lista:
In [20]: a = [1, 2, 3]
b = a
a[1] = 22
print(a)
print(b)
[1, 22, 3]
[1, 22, 3]

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.

Chamando Funções com Objetos Mutáveis como


Argumentos
Se você vem do VBA, provavelmente está acostumado a marcar argumentos de função
como passagem por referência (ByRef) ou passagem por valor (ByVal): quando você passa
uma variável para uma função como argumento , a função terá a capacidade de alterá-lo
(ByRef) ou
Página 290 Apêndice C
trabalhará em uma cópia dos valores (ByVal), deixando a variável original intocada. ByRef é
o padrão no VBA. Considere a seguinte função no VBA:
Function increment(ByRef x As Integer) As Integer
x = x + 1
increment = x
End Function

Então, chame a função assim:


Sub call_increment()
Dim a As Integer
a = 1
Debug.Print increment(a)
Debug.Print a
End Sub

Isso imprimirá o seguinte:


2
2

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

Agora vamos repetir o amostra com um objeto mutável:


In [30]: def increment(x):
x[0] = x[0] + 1
return x
In [31]: a = [1]
print(increment(a))
print(a)
[2]
[2]
Página 291 Apêndice C
Se o objeto for mutável e você quiser deixar o objeto original inalterado, você precisará
passar uma cópia do objeto:
In [32]: a = [1]
print(increment(a.copy()))
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ê!

Funções com objetos mutáveis como argumentos


padrão
Ao escrever funções, você normalmente não deve usar objetos mutáveis como argumentos
padrão. A razão é que o valor dos argumentos padrão é avaliado apenas uma vez como parte
da definição da função, não sempre que a função é chamada. Portanto, usar objetos
mutáveis como argumentos padrão pode levar a um comportamento inesperado:
In [33]: # Don't do this:
def add_one(x=[]):
x.append(1)
return x
In [34]: add_one()
Out[34]: [1]
In [35]: add_one()
Out[35]: [1, 1]

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]

You might also like