Seus primeiros passos como Data Scientist: Introdução ao Pandas!
Além desses animaizinhos simpáticos, Pandas também é uma biblioteca Python. Ela fornece ferramentas de análise de dados e estruturas de dados de alta performance e fáceis de usar.
Por ser a principal e mais completa biblioteca para estes objetivos, Pandas é fundamental para Análise de Dados.
Disclaimer
Esse guia foi escrito como uma alternativa em português às introduções já existentes e à introdução de 10 minutos apresentada na documentação oficial, e tem por objetivo fornecer de forma enxuta e simplificada uma apresentação básica às principais ferramentas fornecidas pelo pandas, cobrindo:
- Manipulação,
- Leitura,
- Visualização de dados.
A introdução pressupõe apenas conhecimento básico em Python.
Há duasoutras excelentes opções de acessar esta introdução:
- Você pode acessar o MyBinder deste arquivo, que cria um ambiente interativo Jupyter rodando Python com todas as dependências necessárias automaticamente, onde você pode testar e executar por si mesmo as linhas de código deste tutorial direto do seu navegador sem precisar configurar nada.
- Você pode acessar o notebook viewer deste arquivo, que fornece syntax highlighting e formatação mais padronizada com o que se está acostumado num notebook Jupyter.
Mãos à obra!
Vamos começar com as importações, usaremos além do pandas, o numpy, biblioteca para computação científica e o matplotlib, biblioteca principal para visualização de dados, entretanto, como veremos mais adiante, o próprio pandas nos fornece facilidades em relação à visualização de dados, com métodos construídos com base no matplotlib, também importamos esta biblioteca para, além de poder modificar esteticamente nossos gráficos, facilitar a exibição dos gráficos. A linha %matplotlib inline
faz parte da mágica do Jupyter e você não deve rodá-la caso esteja em outra IDE/Ambiente.
import pandas as pdimport numpy as npimport matplotlib.pyplot as plt%matplotlib inline
Existem dois tipos principais de estruturas de dados no pandas:
Series
Uma Series é como um array unidimensional, uma lista de valores. Toda Series possui um índice, o index
, que dá rótulos a cada elemento da lista. Abaixo criamos uma Series notas
, o index
desta Series é a coluna à esquerda, que vai de 0 a 4 neste caso, que o pandas criou automaticamente, já que não especificamos uma lista de rótulos.
notas = pd.Series([2,7,5,10,6])notas
02172531046dtype: int64
Já podemos aqui verificar os atributos da nossa Series, comecemos pelos valores e o índice, os dois atributos fundamentais nesta estrutura:
notas.values
array([ 2,7,5, 10,6])
notas.index
RangeIndex(start=0, stop=5, step=1)
Como ao criar a Series não demos um índice específico o pandas usou os inteiros positivos crescentes como padrão. Pode ser conveniente atribuirmos um índice diferente do padrão, supondo que essas sejam notas de uma turma, poderíamos atribuir nomes ao index:
notas = pd.Series([2,7,5,10,6], index=["Wilfred", "Abbie", "Harry", "Julia", "Carrie"])notas
Wilfred2Abbie7Harry5Julia10Carrie6dtype: int64
O index nos ajuda para referenciar um determinado valor, ele nos permite acessar os valores pelo seu rótulo:
notas["Julia"]
10
Outra facilidade proporcionada pela estrutura são seus métodos que fornecem informações estatísticas sobre os valores, como média .mean()
e desvio padrão .std()
. Encorajo o leitor(a) a investigar e verificar alguns dos métodos e atributos da estrutura usando o TAB
para auto-completação na shell do Python, ou simplesmente checar a completíssima documentação oficial deste objeto.
print("Média:", notas.mean())print("Desvio padrão:", notas.std())
Média: 6.0Desvio padrão: 2.9154759474226504
Geralmente para resumir brevemente as estatísticas dos dados se usa o .describe()
notas.describe()
count5.000000mean6.000000std2.915476min2.00000025%5.00000050%6.00000075%7.000000max10.000000dtype: float64
A estrutura é flexível o suficiente pra aplicarmos algumas expressões matemáticas e funções matemáticas do numpy diretamente:
notas**2
Wilfred4Abbie49Harry25Julia100Carrie36dtype: int64
np.log(notas)
Wilfred0.693147Abbie1.945910Harry1.609438Julia2.302585Carrie1.791759dtype: float64
DataFrame
Já um DataFrame é uma estrutura bidimensional de dados, como uma planilha. Abaixo criaremos um DataFrame que possui valores de diferentes tipos, usando um dicionário como entrada dos dados:
df = pd.DataFrame({'Aluno' : ["Wilfred", "Abbie", "Harry", "Julia", "Carrie"],'Faltas' : [3,4,2,1,4],'Prova' : [2,7,5,10,6],'Seminário': [8.5,7.5,9.0,7.5,8.0]})df
Os tipos de dados que compõe as colunas podem ser verificados por um método próprio:
df.dtypes
AlunoobjectFaltasint64Provaint64Semináriofloat64dtype: object
É possível acessar a lista de colunas de forma bem intuitiva:
df.columns
Index(['Aluno', 'Faltas', 'Prova', 'Seminário'], dtype='object')
Os nomes das colunas podem ser usadas pra acessar seus valores:
df["Seminário"]
08.517.529.037.548.0Name: Seminário, dtype: float64
Para DataFrames, .describe()
também é uma boa forma de verificar resumidamente a disposição estatística dos dados numéricos:
df.describe()
Outra tarefa comum aplicada em DataFrames é ordená-los por determinada coluna:
df.sort_values(by="Seminário")
Note que simplesmente usar o método sort_values
não modifica o nosso DataFrame original:
df
Muitas vezes é necessário selecionarmos valores específicos de um DataFrame, seja uma linha ou uma célula específica, e isso pode ser feito de diversas formas. A documentação oficial contém vasta informação para esse tipo de tarefa, aqui nos concentraremos nas formas mais comuns de selecionarmos dados.
Para selecionar pelo index ou rótulo usamos o atributo .loc
:
df.loc[3]
AlunoJuliaFaltas1Prova10Seminário7.5Name: 3, dtype: object
Para selecionar de acordo com critérios condicionais, se usa o que se chama de Boolean Indexing.
Suponha que queiramos selecionar apenas as linhas em que o valor da coluna Seminário seja acima de 8.0, podemos realizar esta tarefa passando a condição diretamente como índice:
df[df["Seminário"] > 8.0]
Este tipo de indexação também possibilita checar condições de múltiplas colunas. Diferentemente do que estamos habituados em Python, aqui se usam operadores bitwise, ou seja, &
, |
, ~
ao invés de and
, or
, not
, respectivamente. Suponha que além de df["Seminário"] > 8.0
queiramos que o valor da coluna Prova
não seja menor que 3:
df[(df["Seminário"] > 8.0) & (df["Prova"] > 3)]
Por enquanto é isso para manipulação de Series e DataFrames, conforme a seção de leitura de dados for se estendendo irei aprensentar alguns outros métodos dessas estruturas que poderão ser interessantes no contexto.
Leitura de Dados
Na seção anterior vimos como manipular dados que foram criados durante esta apresentação, acontece que, na maioria das vezes, queremos analisar dados que já estão prontos. O pandas nos fornece uma série de funcionalidades de leitura de dados, pros mais diversos formatos estruturais de dados, experimente a auto-completação de pd.read_<TAB>
, entre eles estão:
pd.read_csv
, para ler arquivos .csv, formato comum de armazenar dados de tabelaspd.read_xlsx
, para ler arquivos Excel .xlsx, é necessário instalar uma biblioteca adicional pra esta funcionalidade.pd.read_html
, para ler tabelas diretamente de um website
Usaremos para analisar dados externos nesta introdução o .read_csv
, pois é neste formato que se encontram nossos dados. CSV, ou comma-separated values é um formato muito comum de dados abertos, trata-se, como a sigla sugere, de valores divididos por vírgula, apesar de o caracter separador poder ser o ponto-e-vírgula ou outro.
O arquivo dados.csv
está na mesma pasta do nosso script, então podemos passar como argumento do .read_csv
apenas o seu nome. Outro argumento interessante da função é o sep
, que por padrão é a vírgula, mas que pode ser definido como outro caractere caso seu dado esteja usando outro separador.
Estes dados que usaremos como exemplo são dados sobre preços de apartamentos em 7 bairros da cidade do Rio de Janeiro: Botafogo, Copacabana, Gávea, Grajaú, Ipanema, Leblon, Tijuca. Os dados podem ser encontrados aqui (Basta baixar diretamente ou copiar o texto pro seu editor preferido e salvar como dados.csv).
df = pd.read_csv("dados.csv")df
Como esperado, o DataFrame tem muitas linhas de dados, pra visualizar sucintamente as primeiras linhas de um DataFrame existe o método .head()
df.head()
Por padrão .head()
exibe as 5 primeiras linhas, mas isso pode ser alterado:
df.head(n=10)
Similarmente existe o .tail()
, que exibe por padrão as últimas 5 linhas do DataFrame:
df.tail()
Manipulação de Dados
Além de confiar em mim, quando mencionei os bairros que continham no nosso conjunto de dados, você pode verificar a informação usando um método que lista os valores únicos numa coluna:
df["bairro"].unique()
array(['Botafogo', 'Copacabana', 'Gávea', 'Grajaú', 'Ipanema', 'Leblon','Tijuca'], dtype=object)
Também parece interessante verificarmos a hegemoneidade da nossa amostra em relação aos bairros. Pra tarefas de contar valores podemos sempre aproveitar de outro método disponível, o .value_counts()
, também veremos um pouco mais abaixo como visualizar estes valores em forma de gráfico de barras.
df["bairro"].value_counts()
Copacabana346Tijuca341Botafogo307Ipanema281Leblon280Grajaú237Gávea205Name: bairro, dtype: int64
Os valores contados também podem ser normalizados para expressar porcentagens:
df["bairro"].value_counts(normalize=True)
Copacabana0.173260Tijuca0.170756Botafogo0.153731Ipanema0.140711Leblon0.140210Grajaú0.118678Gávea0.102654Name: bairro, dtype: float64
Agrupar os dados se baseando em certos critérios é outro processo que o pandas facilita bastante com o .groupby()
. Esse método pode ser usado para resolver os mais amplos dos problemas, aqui abordarei apenas o agrupamento simples, a divisão de um DataFrame em grupos.
Abaixo agrupamos o nosso DataFrame pelos valores da coluna "bairro"
, e em seguida aplicamos o .mean()
para termos um objeto GroupBy com informação das médias agrupadas pelos valores da coluna bairros.
df.groupby("bairro").mean()
Para extrairmos dados de uma coluna deste objeto basta acessá-lo convencionalmente, para obtermos os valores da média do preço do metro quadrado em ordem crescente, por exemplo:
df.groupby("bairro").mean()["pm2"].sort_values()
bairroGrajaú6145.624473Tijuca7149.804985Copacabana11965.298699Botafogo12034.486189Gávea16511.582780Ipanema19738.407794Leblon20761.351036Name: pm2, dtype: float64
É comum queremos aplicar uma função qualquer aos dados, ou à parte deles, neste caso o pandas fornece o método .apply
. Por exemplo, para deixar os nomes dos bairros como apenas as suas três primeiras letras:
def truncar(bairro):return bairro[:3]
df["bairro"].apply(truncar)
0Bot1Bot2Bot3Bot4Bot5Bot6Bot7Bot8Bot9Bot10Bot...1987Tij1988Tij1989Tij1990Tij1991Tij1992Tij1993Tij1994Tij1995Tij1996TijName: bairro, Length: 1997, dtype: object
Ou de um jeito mais prático, usando uma função lambda:
df["bairro"].apply(lambda x: x[:3])
0Bot1Bot2Bot3Bot4Bot5Bot6Bot7Bot8Bot9Bot10Bot...1986Tij1987Tij1988Tij1989Tij1990Tij1991Tij1992Tij1993Tij1994Tij1995Tij1996TijName: bairro, Length: 1997, dtype: object
Uma das tarefas na qual o pandas é reconhecidamente poderoso é a habilidade de tratar dados incompletos. Por muitos motivos pode haver incompletude no dataset, o np.nan
é um valor especial definido no Numpy, sigla para Not a Number, o pandas preenche células sem valores em um DataFrame lido com np.nan
.
Vamos criar um novo dataframe usando as 5 primeiras linhas do nosso original, usando o já visto .head()
. Abaixo é usado o .replace
para substituir um valor específico por um NaN
.
df2 = df.head()df2 = df2.replace({"pm2": {12031.25: np.nan}})df2
O pandas simplifica a remoção de quaiquer linhas ou colunas que possuem um np.nan
, por padrão o .dropna()
retorna as linhas que não contém um NaN:
df2.dropna()
Preencher todos os valores NaN por um outro específico também é bastante simples:
df2.fillna(99)
Acaba sendo muitas vezes conveniente termos um método que indica quais valores de um dataframe são NaN e quais não são:
df2.isna()
Visualização de Dados
Partiremos agora para visualização de dados com o pandas. Os métodos de visualização do pandas são construídos com base no matplotlib para exploração rápida dos dados. Para se ter mais liberdade no conteúdo e possibilidades de visualização se recomenda usar diretamente o matplotlib ou ainda, para visualização estatística, o seaborn. Nesta introdução tratarei apenas dos métodos de visualização incluídos no pandas, que por outro lado, oferece uma sintaxe bastante simples para realizar a tarefa.
Comecemos verificando que tanto Series como DataFrame possuem um método .plot()
que também é um atributo e pode ser encadeado para gerar visualização de diversos tipos, como histograma, área, pizza e dispersão, com respectivamente .hist()
, .area()
, .pie()
e .scatter()
, além de vários outros.
Vamos verificar a distribuição dos preços usando o encadeamento .plot.hist()
, o eixo x, que é o preço, está numa escala de *10^7, como mostrado na imagem:
df["preco"].plot.hist()
Por padrão esse método usa 10 bins, ou seja, divide os dados em 10 partes, mas é claro que podemos especificar um valor para a plotagem. Abaixo, além de especificar a quantidade de bins, também especifiquei a cor das bordas como preta, que por padrão é transparente.
df["preco"].plot.hist(bins=30, edgecolor='black')
Podemos usar os valores de contagem de cada bairro como exemplo de dado para um plot tanto de barras verticais quando de barras horizontais, para verificar visualmente esses dados:
df["bairro"].value_counts().plot.bar()
df["bairro"].value_counts().plot.barh()
Os métodos são flexíveis o suficiente para aceitarem argumentos como um título para a imagem:
df["bairro"].value_counts().plot.barh(title="Número de apartamentos")
Um gráfico de dispersão usando um DataFrame pode ser usado especificando-se quais colunas usar como dados no eixo x e y:
df.plot.scatter(x='preco', y='area')
Para fins estéticos, o matplotlib fornece uma série de styles diferentes que podem ser usados, um deles é o ggplot
plt.style.use('ggplot')
Agora este estilo será usado em todas as imagens geradas após essa linha
df.plot.scatter(x='pm2', y='area')
A lista de estilos disponíveis pode ser vista através de um método próprio
plt.style.available
['bmh','Solarize_Light2','seaborn-talk','seaborn-bright','seaborn-white','seaborn-pastel','seaborn-ticks','seaborn-dark-palette','seaborn','tableau-colorblind10','seaborn-deep','classic','seaborn-dark','grayscale','seaborn-paper','fivethirtyeight','seaborn-muted','_classic_test','seaborn-poster','seaborn-notebook','seaborn-darkgrid','seaborn-colorblind','dark_background','seaborn-whitegrid','ggplot','fast']
A coluna de quartos diz quantos quartos tem um determinado apartamento, também se pode ver a contagem e distribuição usando outros métodos de plotagem oferecidos pelo pandas:
df["quartos"].value_counts().plot.pie()
Uma coisa a se notar do gráfico de scatter é a poluição causada pela enorme quantidade de dados agrupadas num dos cantos do gráfico, além de podermos diminuir o tamanho dos pontos passando o argumento s
ao método .scatter
podemos também usar um método do pandas que cria uma amostragem aleatória dos dados.
O .sample
pode receber tanto um argumento frac
, que determina uma fração dos itens que o método retornará (no caso abaixo, 10%), ou n
, que determina um valor absoluto de itens.
df.plot.scatter(x='preco', y='area', s=.5)
df.sample(frac=.1).plot.scatter(x='preco', y='area')
Finalmente, a tarefa de salvar seu DataFrame externamente para um formato específico é feita com a mesma simplicidade que a leitura de dados é feita no pandas, pode-se usar, por exemplo, o método to_csv
, e o arquivo será criado com os dados do DataFrame:
df = pd.DataFrame({'Aluno' : ["Wilfred", "Abbie", "Harry", "Julia", "Carrie"],'Faltas' : [3,4,2,1,4],'Prova' : [2,7,5,10,6],'Seminário': [8.5,7.5,9.0,7.5,8.0]})df.to_csv("aulas.csv")
pd.read_csv("aulas.csv")
Com o que foi abordado nesta introdução você já deve estar apto a fazer exploração e manipulação básica de dados com o pandas, para aprofundar mais aqui vão algumas referências: