O Pandas é uma biblioteca Python para exploração, análise e manipulação de dados. Ela possui muitas ferramentas que nos auxiliam a trabalhar com bancos de dados como o MySQL, dados baseados em arquivos de texto como csv, json, html ou arquivos do Excel.
Open Source, poderoso e com uma curva de aprendizado baixa, é uma ótima ferramenta para iniciantes na linguagem Python e uma alternativa muito mais robusta para pessoas que usam o Excel como ferramenta de análise.
A Análise Exploratória de Dados (AED) é uma etapa fundamental no processo de análise de dados em ciência de dados e estatística. Ela envolve a exploração e o exame inicial dos dados para entender suas características, padrões, tendências e anomalias antes de aplicar técnicas mais avançadas de modelagem ou inferência estatística. A principal finalidade da AED é resumir, visualizar e compreender os dados, o que pode fornecer insights valiosos para direcionar as etapas subsequentes da análise de dados.
Neste artigo vamos utilizar um pouco da AED para aprender o básico de Pandas. De quebra teremos um conhecimento da ferramenta capaz de ajudar a iniciar análises de uma série de problemas do mundo real.
Para começar a utilizar o Pandas você não precisa instalar nenhum software, apenas acesse uma ferramenta online como Google Colab. É possível também utilizar o Jupyter Notebook instalado em sua máquina ou mesmo baixar um projeto que fiz e disponibilizei em meu GitHub que simplifica o processo de rodar o Jupyter Notebook em sua máquina utilizado o Docker.
Este post está longe de esgotar o assunto pois a ideia é apenas mostrar os conceitos básicos do Pandas como uma espécie de guia de consulta. Para isto vamos utilizar a base de dados Estimativa de População 2021 do IBGE para treinar e fixar os conceitos.
Fonte original dos dados: https://www.ibge.gov.br/estatisticas/downloads-estatisticas.html
Estimativas_de_Populacao > Estimativas_2021 > estimativa_dou_2021.xls
Porém para este guia preparei os dados de forma a facilitar nosso trabalho. Eles podem ser baixados aqui:
pd é o apelido que a comunidade dá ao pandas.
import pandas as pd
No Google Colab ou Jupyter Notebook o Pandas já vem instalado, sendo assim não é necessário nenhum esforço extra além do comando acima :).
Deixei o arquivo estimativa_dou_2021.csv pré formatado de forma a carregar de maneira mais fácil.
Aqui a única observação é indicar o tipo de separador das colunas, no caso sep=";" e forçar a exibição dos dados como string para não perdermos nenhuma informação já que por padrão o Pandas tenta interpretar o formato dos dados.
df = pd.read_csv("estimativa_dou_2021.csv", sep=";", dtype=str)
df.head()
Por padrão .head() retorna cinco registros.
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
0 | RO | 11 | 00015 | Alta Floresta D'Oeste | 22.516 |
1 | RO | 11 | 00023 | Ariquemes | 111.148 |
2 | RO | 11 | 00031 | Cabixi | 5.067 |
3 | RO | 11 | 00049 | Cacoal | 86.416 |
4 | RO | 11 | 00056 | Cerejeiras | 16.088 |
type(df)
pandas.core.frame.DataFrame
O Pandas nos retorna um DataFrame que é um formado tabular de dados, semelhante ao que seria uma tabela no Excel.
É possivel criar um Data Frame manualmente também. Segue um exemplo abaixo
data = {'nome': ['Ana', 'João', 'Beatriz'], 'idade': [23, 18, 86]}
df = pd.DataFrame(data, columns=['nome','idade'])
nome | idade | |
---|---|---|
0 | Ana | 23 |
1 | João | 18 |
2 | Beatriz | 86 |
Referência: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html
Outras formas de abrir arquivos de dados:
// Por padrão tenta abrir um csv com separação por vírgulas
pd.read_csv('filename')
// Abre com encode iso-8859-1
pd.read_csv('filename', encoding='iso-8859-1')
// Carrega apenas algumas linhas do .csv. Ex: 10 linhas
pd.read_csv('filename', nrows=10)
// Carrega dados com nomes de colunas personalizadas
pd.read_csv('filename', names=['Column #1','Column #2'], engine='python')
// Carrega dados de algumas colunas (menor utilização de memória)
pd.read_csv('filename', usecols=[0,1,2...])
pd.read_csv('filename', usecols=['Column #1','Column #2'])
// Carrega dados de arquivos .tsv
pd.read_table('filename')
// Carrega json
pd.read_json('json source')
// Carrega dados de um documento HTML
pd.read_html('html source')
// Carrega dados de uma conexão SQL
pd.read_sql(query, connection_configs)
É importante uma pré visualização de como está formatado nosso DataFrame para que consigamos avaliar se será preciso corrigir, limpar ou modificar algo. Existem dois métodos que podem fazer isso rapidamente, o .info() e o .describe(). Vamos utiliza-los para olhar dentro de nosso DataFrame.
df.info()
RangeIndex: 5570 entries, 0 to 5569
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 UF 5570 non-null object
1 COD. UF 5570 non-null object
2 COD. MUNIC 5570 non-null object
3 NOME DO MUNICÍPIO 5570 non-null object
4 POPULAÇÃO ESTIMADA 5570 non-null object
dtypes: object(5)
memory usage: 217.7+ KB
df.describe()
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
count | 5570 | 5570 | 5570 | 5570 | 5570 |
unique | 27 | 27 | 4307 | 5297 | 5110 |
top | MG | 31 | 00708 | Bom Jesus | 6.232 |
freq | 853 | 853 | 4 | 5 | 4 |
Olhando os retornos acima podemos notar alguns problemas neste DataFrame. Precisamos atuar neles para corrigi-los.
O campo 'POPULAÇÃO ESTIMADA' na verdade está como ' POPULAÇÃO ESTIMADA ' com espaços no início e final do nome. Vamos corrigir:
df.rename(columns={' POPULAÇÃO ESTIMADA ': 'POPULAÇÃO ESTIMADA'}, inplace=True)
df
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
0 | RO | 11 | 00015 | Alta Floresta D'Oeste | 22.516 |
1 | RO | 11 | 00023 | Ariquemes | 111.148 |
2 | RO | 11 | 00031 | Cabixi | 5.067 |
3 | RO | 11 | 00049 | Cacoal | 86.416 |
4 | RO | 11 | 00056 | Cerejeiras | 16.088 |
... | ... | ... | ... | ... | ... |
5565 | GO | 52 | 22005 | Vianópolis | 14.088 |
5566 | GO | 52 | 22054 | Vicentinópolis | 9.002 |
5567 | GO | 52 | 22203 | Vila Boa | 6.451 |
5568 | GO | 52 | 22302 | Vila Propício | 5.941 |
5569 | DF | 53 | 00108 | Brasília | 3.094.325 |
5570 rows × 5 columns
O campo 'POPULAÇÃO ESTIMADA' está formatado com ponto de milhar e está em formado de string ou objeto. Este campo não é um objeto mas sim uma variável quantitativa discreta.
É preciso modificar isso retirando o ponto de milhar e convertendo o campo de Objeto para Inteiro
df['POPULAÇÃO ESTIMADA'] = df['POPULAÇÃO ESTIMADA'].str.replace('.','')
df['POPULAÇÃO ESTIMADA'] = df['POPULAÇÃO ESTIMADA'].astype(int)
df.info()
RangeIndex: 5570 entries, 0 to 5569
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 UF 5570 non-null object
1 COD. UF 5570 non-null object
2 COD. MUNIC 5570 non-null object
3 NOME DO MUNICÍPIO 5570 non-null object
4 POPULAÇÃO ESTIMADA 5570 non-null int64
dtypes: int64(1), object(4)
memory usage: 217.7+ KB
Agora nosso DataFrame está pronto para exploração, porém vamos fazer o mesmo processo utilizando um arquivo excel.
A planilha "estimativa_dou_2021.xls" possui os mesmos dados do arquivo csv anterior porém com algumas linha a mais e também está em um formato antigo do Excel.
Para a correta interpretação do arquivo pelo Pandas precisamos utilizar a engine "xlrd".
Como a planilha possui duas abas se não indicamos qual aba queremos o pandas abre por padrão a primeira. Selecionamos a aba "Municípios" que é a correta para esta análise.
Como a planilha possui um cabeçalho que não nos interessa para esta análise pulamos a primeira linha com o parâmetro skiprows=[0].
Outro ponto de atenção é que por padrão o Pandas tenta interpretar o tipo de cada campo automaticamente o que pode gerar perda de infornação para a análise. Quando não se sabe ao certo como são os dados é interessante forçar a coversão de todos os campos para o formato de string.
df = pd.read_excel("estimativa_dou_2021.xls", engine="xlrd", skiprows=[0], sheet_name="Municípios", dtype=str)
Vamos fazer uma análise prévia de como os dados estão, para isso utilizamos o .head() e o .tail() que por padrão listam respectivamente as primeiras e últimas cinco linhas.
Podemos também fornecer a quantidade de linhas desejadas como em .tail(30) que listará as 30 últimas linhas
df.head()
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
0 | RO | 11 | 00015 | Alta Floresta D'Oeste | 22516 |
1 | RO | 11 | 00023 | Ariquemes | 111148 |
2 | RO | 11 | 00031 | Cabixi | 5067 |
3 | RO | 11 | 00049 | Cacoal | 86416 |
4 | RO | 11 | 00056 | Cerejeiras | 16088 |
df.tail(30)
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
5563 | GO | 52 | 21858 | Valparaíso de Goiás | 175720 |
5564 | GO | 52 | 21908 | Varjão | 3848 |
5565 | GO | 52 | 22005 | Vianópolis | 14088 |
5566 | GO | 52 | 22054 | Vicentinópolis | 9002 |
5567 | GO | 52 | 22203 | Vila Boa | 6451 |
5568 | GO | 52 | 22302 | Vila Propício | 5941 |
5569 | DF | 53 | 00108 | Brasília | 3094325 |
5570 | NaN | NaN | NaN | NaN | NaN |
5571 | Fonte: IBGE. Diretoria de Pesquisas - DPE - C... | NaN | NaN | NaN | NaN |
5572 | NaN | NaN | NaN | NaN | NaN |
5573 | Notas: | NaN | NaN | NaN | NaN |
5574 | (1) População judicial do município de Porto V... | NaN | NaN | NaN | NaN |
5575 | (2) População judicial do município de Benjami... | NaN | NaN | NaN | NaN |
5576 | (3) População judicial do município de Caapira... | NaN | NaN | NaN | NaN |
5577 | (4) População judicial do município de Guajará... | NaN | NaN | NaN | NaN |
5578 | (5) População judicial do município de Jutaí-A... | NaN | NaN | NaN | NaN |
5579 | (6) População judicial do município de Lábrea-... | NaN | NaN | NaN | NaN |
5580 | (7) População judicial do município de Manaqui... | NaN | NaN | NaN | NaN |
5581 | (8) População judicial do município de Parinti... | NaN | NaN | NaN | NaN |
5582 | (9) População judicial do município de Santa I... | NaN | NaN | NaN | NaN |
5583 | (10) População judicial do município de Tabati... | NaN | NaN | NaN | NaN |
5584 | (11) População judicial do município de Urucar... | NaN | NaN | NaN | NaN |
5585 | (12) População judicial do município de Urucur... | NaN | NaN | NaN | NaN |
5586 | (13) População judicial do município de Jacare... | NaN | NaN | NaN | NaN |
5587 | (14) População judicial do município de Paço d... | NaN | NaN | NaN | NaN |
5588 | (15) População judicial do município de Ferrei... | NaN | NaN | NaN | NaN |
5589 | (16) População judicial do município Coronel J... | NaN | NaN | NaN | NaN |
5590 | (17) População judicial do município Ibiassucê... | NaN | NaN | NaN | NaN |
5591 | (18) População judicial do município de Rodela... | NaN | NaN | NaN | NaN |
5592 | (19) População judicial do município de Vera C... | NaN | NaN | NaN | NaN |
df.describe()
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
count | 5591 | 5570 | 5570 | 5570 | 5570 |
unique | 48 | 27 | 4307 | 5297 | 5110 |
top | MG | 31 | 00708 | Bom Jesus | 6232 |
freq | 853 | 853 | 4 | 5 | 4 |
info() e describe() são ferramentas importantes para verificar o estado bruto em que se encontram nossos dados, ver quantidades e se existem valores nulos.
Pela análise acima podemos observar que existem alguns campos com valores nulos (NaN), estes campos são campos de descrição que não precisamos manter, por isso vamos corrigir isso.
df = df.dropna(subset=['POPULAÇÃO ESTIMADA'])
Poderíamos ter usado um df.dropna() que consideraria todos os campos com NaN mas por segurança indicamos o campo de referência 'POPULAÇÃO ESTIMADA', ou seja, todas as linhas que tenham este campo com NaN serão eliminadas
df.tail()
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
5565 | GO | 52 | 22005 | Vianópolis | 14088 |
5566 | GO | 52 | 22054 | Vicentinópolis | 9002 |
5567 | GO | 52 | 22203 | Vila Boa | 6451 |
5568 | GO | 52 | 22302 | Vila Propício | 5941 |
5569 | DF | 53 | 00108 | Brasília | 3094325 |
df.head()
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
0 | RO | 11 | 00015 | Alta Floresta D'Oeste | 22516 |
1 | RO | 11 | 00023 | Ariquemes | 111148 |
2 | RO | 11 | 00031 | Cabixi | 5067 |
3 | RO | 11 | 00049 | Cacoal | 86416 |
4 | RO | 11 | 00056 | Cerejeiras | 16088 |
df.shape
# (5570, 5)
O comando shape resume o número de linhas e colunas existentes no DataFrame. A limpeza foi concluída com sucesso.
Agora que temos apenas os dados de interesse podemos converter o campo 'POPULAÇÃO ESTIMADA', que é uma variável quantitativa discreta, para inteiro.
df = df.astype({"POPULAÇÃO ESTIMADA":"int"})
df.info()
Index: 5570 entries, 0 to 5569
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 UF 5570 non-null object
1 COD. UF 5570 non-null object
2 COD. MUNIC 5570 non-null object
3 NOME DO MUNICÍPIO 5570 non-null object
4 POPULAÇÃO ESTIMADA 5570 non-null int64
dtypes: int64(1), object(4)
memory usage: 261.1+ KB
Sendo assim chegamos no mesmo estado do DataFrame carregado pelo arquivo csv. Podemos prosseguir com a análise exploratória dos dados.
Com o DataFrame estruturado corretamente podemos iniciar a exploração dos dados, tanto de maneira geral quanto olhando detalhes que sejam interessantes.
df[['UF','COD. UF']].describe()
UF | COD. UF | |
---|---|---|
count | 5570 | 5570 |
unique | 27 | 27 |
top | MG | 31 |
freq | 853 | 853 |
df['POPULAÇÃO ESTIMADA'].describe()
count 5.570000e+03
mean 3.829760e+04
std 2.242882e+05
min 7.710000e+02
25% 5.454000e+03
50% 1.173200e+04
75% 2.576475e+04
max 1.239637e+07
Name: POPULAÇÃO ESTIMADA, dtype: float64
Minas Gerais aparece como o TOP das ocorrências de estados. Isto é compreensível já que os dados se referem a todos os municipios do Brasil e Minas Gerais é o estado com maior número de municipios (853).
A população média por município é de 38.297,6 habitantes porém temos um municipio (ou mais) com aprox. 12.396.370 habitantes e um (ou mais) com 771 habitantes. Que municípios serão estes?
Vamos descobrir isso olhando dentro do DataFrame pelo número de habitantes.
municipio_menos_populoso = df['POPULAÇÃO ESTIMADA'].min()
municipio_menos_populoso
# 771
df[df['POPULAÇÃO ESTIMADA'] == municipio_menos_populoso]
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
3026 | MG | 31 | 66600 | Serra da Saudade | 771 |
O município brasileiro com o menor número de habitantes é 'Serra da Saudade' que fica em Minas Gerais
municipio_mais_populoso = df['POPULAÇÃO ESTIMADA'].max()
df[df['POPULAÇÃO ESTIMADA'] == municipio_mais_populoso]
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
3829 | SP | 35 | 50308 | São Paulo | 12396372 |
Como já desconfiávamos o município com a maior população é São Paulo.
Já que estamos falando da cidade de São Paulo e do estado de Minas Gerais vamos olhar ambos com mais detalhes. Para isso vamos fazer um agrupamento geral.
Mas antes de seguir para São Paulo e Minas Gerais vamos fazer um apanhado geral dos municípios e população para cada estado, o que facilitará nosso trabalho futuro.
total_municipios_por_uf = df.groupby('UF')['COD. MUNIC'].count()
total_municipios_por_uf
UF
AC 22
AL 102
AM 62
AP 16
BA 417
CE 184
DF 1
ES 78
GO 246
MA 217
MG 853
MS 79
MT 141
PA 144
PB 223
PE 185
PI 224
PR 399
RJ 92
RN 167
RO 52
RR 15
RS 497
SC 295
SE 75
SP 645
TO 139
Name: COD. MUNIC, dtype: int64
type(total_municipios_por_uf)
# pandas.core.series.Series
total_municipios_por_uf.count()
# 27
O agrupamento acima por UF criou uma estrutura do pandas chamada Series. Cada coluna de um DataFrame é uma Series.
Referência: https://pandas.pydata.org/docs/reference/api/pandas.Series.html
A lista acima traz o total de municípios por unidade da federação que é composta por 26 estados e um Distrito Federal (27 UFs).
Podemos somar todos municípios e confirmar se o total bate com o número 5570.
total_municipios_por_uf.sum()
# 5570
populacao_por_uf = df.groupby('UF')['POPULAÇÃO ESTIMADA'].sum()
populacao_por_uf
UF
AC 906876
AL 3365351
AM 4269995
AP 877613
BA 14985284
CE 9240580
DF 3094325
ES 4108508
GO 7206589
MA 7153262
MG 21411923
MS 2839188
MT 3567234
PA 8777124
PB 4059905
PE 9674793
PI 3289290
PR 11597484
RJ 17463349
RN 3560903
RO 1815278
RR 652713
RS 11466630
SC 7338473
SE 2338474
SP 46649132
TO 1607363
Name: POPULAÇÃO ESTIMADA, dtype: int64
A população brasileira pode ser obtida de duas formas, ou somando os totais por estado ou somando todos os municípios direto do DataFrame principal.
populacao_brasil = populacao_por_uf.sum()
populacao_brasil
# 213317639
df['POPULAÇÃO ESTIMADA'].sum()
# 213317639
Ambos os números batem em 213.317.639
Vamos ordenar as unidades da federação e municípios pela quantidade decrescente de habitantes.
Veja que nos exemplos abaixo a ordenação é feita tanto a partir de uma Series quanto de um DataFrame e que cada tipo de estrutura tem uma sintaxe específica.
populacao_por_uf.sort_values(ascending=False)
UF
SP 46649132
MG 21411923
RJ 17463349
BA 14985284
PR 11597484
RS 11466630
PE 9674793
CE 9240580
PA 8777124
SC 7338473
GO 7206589
MA 7153262
AM 4269995
ES 4108508
PB 4059905
MT 3567234
RN 3560903
AL 3365351
PI 3289290
DF 3094325
MS 2839188
SE 2338474
RO 1815278
TO 1607363
AC 906876
AP 877613
RR 652713
Name: POPULAÇÃO ESTIMADA, dtype: int64
df.sort_values(by="POPULAÇÃO ESTIMADA", ascending=False)
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
3829 | SP | 35 | 50308 | São Paulo | 12396372 |
3242 | RJ | 33 | 04557 | Rio de Janeiro | 6775561 |
5569 | DF | 53 | 00108 | Brasília | 3094325 |
2162 | BA | 29 | 27408 | Salvador | 2900319 |
949 | CE | 23 | 04400 | Fortaleza | 2703391 |
... | ... | ... | ... | ... | ... |
5077 | RS | 43 | 22350 | União da Serra | 1084 |
4749 | RS | 43 | 06924 | Engenho Velho | 932 |
5192 | MT | 51 | 01209 | Araguainha | 909 |
3348 | SP | 35 | 07209 | Borá | 839 |
3026 | MG | 31 | 66600 | Serra da Saudade | 771 |
5570 rows × 5 columns
Primeiro vamos confirmar a quantidade de municipios no estado de Minas Gerais e depois vamos ver a população total do estado.
Obter o total para Minas Gerais é fácil, basta selecionar pelo índice da Series. Os índices desta Series são as siglas dos estados.
total_municipios_minas_gerais = total_municipios_por_uf.loc['MG']
total_municipios_minas_gerais
# 853
A quantidade de municípios de MG bate com o max de .describe(), confirmando que Minas Gerais é o estado com o maior número de municípios.
populacao_mg = populacao_por_uf.loc['MG']
populacao_mg
# 21411923
A população do estado de Minas Gerais é de 21.411.923 habitantes
Agora vamos olhar para o Estado de São Paulo e contar a quantidade de municípios do estado e sua população total.
total_municipios_sp = total_municipios_por_uf.loc['SP']
total_municipios_sp
# 645
populacao_sp = populacao_por_uf.loc['SP']
populacao_sp
# 46649132
São 645 municípios com uma população total de 46.649.132 habitantes.
Podemos fazer um gráfico simplificado da população por estado utilizando diretamente o Pandas. Existem bibliotetas Python melhores para trabalhar com gráficos como o Seaborn ou Matplotlib porém para tarefas mais simples de exploração o Pandas nos dá uma grande ajuda.
Referência: https://pandas.pydata.org/docs/reference/api/pandas.Series.plot.html
populacao_por_uf.plot(kind="bar", title="População por estado", ylabel="População")
<Axes: title={'center': 'População por estado'}, xlabel='UF', ylabel='População'>
Podemos também verificar se existem e como estão distribuidos outliers nos dados utilizando o boxplot.
Vamos olhar, por exemplo, a população por município.
Referência: https://pandas.pydata.org/docs/reference/api/pandas.Series.plot.box.html
df['POPULAÇÃO ESTIMADA'].plot(kind="box", grid=True, logy=True)
Aqui a primeira observação a ser feita é que devemos plotar utilizando uma escala logaritmica para uma melhor visualização dos dados.
A mediana (linha verde) está um pouco a cima dos 10.000 habitantes o que corresponde ao valor de 11.732 obtido pelo .description() (50% ou segundo quartil)
As bolinhas escuras representam nossos outliers, ou seja, os municípios cujas populações se diferenciam significativamente dos demais valores de nossa base de dados. A bolinha superior mais afastada representa o município de São Paulo.
O gráfico acima é construído através dos valores calculados abaixo sendo que a linha azul, perto dos 100 mil, é o chamado limite superior.
Q1 = df['POPULAÇÃO ESTIMADA'].quantile(0.25, interpolation="nearest")
Q3 = df['POPULAÇÃO ESTIMADA'].quantile(0.75, interpolation="nearest")
IQR = Q3 - Q1
limite_inferior = Q1 - (1.5 * IQR)
limite_superior = Q3 + (1.5 * IQR)
limite_superior
# 56243.0
Vamos utilizar este limite superior, ou seja, 56243 habitantes, para construir um histograma dos dados que se encontram dentro do boxplot.
A ideia do histograma neste caso é contar a quantidade de municípios que está dentro de um intervalo de habitantes (ex: quantos municípios possuem entre 10.000 e 20.000 habitantes).
df[df['POPULAÇÃO ESTIMADA'] <= limite_superior]['POPULAÇÃO ESTIMADA'].plot(kind="hist", title="Histograma de municípios", ylabel="Contagem de municípios", xlabel="Habitantes")
<Axes: title={'center': 'Histograma de municípios'}, xlabel='Habitantes', ylabel='Contagem de municípios'>
O histograma acima pode ser classificado como 'distorcido à direita' e indica que conforme vamos procurando municípios com mais habitantes eles vão se tornando mais raros.
Vamos selecionar uma linha qualquer do DataFrame utilizando o número do index, que no caso de nosso DataFrame por padrão é uma sequência numérica. Vamos escolher o índice 3549
df.loc[3549]
UF SP
COD. UF 35
COD. MUNIC 24907
NOME DO MUNICÍPIO Jambeiro
POPULAÇÃO ESTIMADA 6828
Name: 3549, dtype: object
Um índice melhor para nosso DataFrame seria, ao invés de uma sequencia de números, utilizar um padrão que crie um índice único para cada registro. Vamos fazer isso utilizando os campos 'UF' e 'COD. MUNIC'.
Primeiro vamos criar uma nova coluna chamada COD. GERAL com esta composição de 'UF' e 'COD. MUNIC'
df['COD. GERAL'] = df['UF'] + df['COD. MUNIC']
df
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | COD. GERAL | |
---|---|---|---|---|---|---|
0 | RO | 11 | 00015 | Alta Floresta D'Oeste | 22516 | RO00015 |
1 | RO | 11 | 00023 | Ariquemes | 111148 | RO00023 |
2 | RO | 11 | 00031 | Cabixi | 5067 | RO00031 |
3 | RO | 11 | 00049 | Cacoal | 86416 | RO00049 |
4 | RO | 11 | 00056 | Cerejeiras | 16088 | RO00056 |
... | ... | ... | ... | ... | ... | ... |
5565 | GO | 52 | 22005 | Vianópolis | 14088 | GO22005 |
5566 | GO | 52 | 22054 | Vicentinópolis | 9002 | GO22054 |
5567 | GO | 52 | 22203 | Vila Boa | 6451 | GO22203 |
5568 | GO | 52 | 22302 | Vila Propício | 5941 | GO22302 |
5569 | DF | 53 | 00108 | Brasília | 3094325 | DF00108 |
5570 rows × 6 columns
Agora é transformar a nova coluna no índice do DataFrame
df = df.set_index('COD. GERAL')
df
UF | COD. UF | COD. MUNIC | NOME DO MUNICÍPIO | POPULAÇÃO ESTIMADA | |
---|---|---|---|---|---|
COD. GERAL | |||||
RO00015 | RO | 11 | 00015 | Alta Floresta D'Oeste | 22516 |
RO00023 | RO | 11 | 00023 | Ariquemes | 111148 |
RO00031 | RO | 11 | 00031 | Cabixi | 5067 |
RO00049 | RO | 11 | 00049 | Cacoal | 86416 |
RO00056 | RO | 11 | 00056 | Cerejeiras | 16088 |
... | ... | ... | ... | ... | ... |
GO22005 | GO | 52 | 22005 | Vianópolis | 14088 |
GO22054 | GO | 52 | 22054 | Vicentinópolis | 9002 |
GO22203 | GO | 52 | 22203 | Vila Boa | 6451 |
GO22302 | GO | 52 | 22302 | Vila Propício | 5941 |
DF00108 | DF | 53 | 00108 | Brasília | 3094325 |
5570 rows × 5 columns
Podemos selecionar uma linha pelo novo índice, por exemplo a cidade do Rio de Janeiro
df.loc['RJ04557']
UF RJ
COD. UF 33
COD. MUNIC 04557
NOME DO MUNICÍPIO Rio de Janeiro
POPULAÇÃO ESTIMADA 6775561
Name: RJ04557, dtype: object
Vamos juntar dois DataFrames em um único. Para isso vamos utilizar as duas Series construídas para agrupar quantidade de minicípios e habitantes por UF.
Vamos converter as Series 'total_municipios_por_uf' e 'populacao_por_uf' em um DataFrame.
df_populacao_por_uf = populacao_por_uf.to_frame()
df_municipios_por_uf = total_municipios_por_uf.to_frame()
Vamos juntar os dois DataFrames utilizando o índice 'UF' como chave.
df_uf = pd.merge(df_populacao_por_uf, df_municipios_por_uf, how='inner', on='UF')
df_uf.head(2)
POPULAÇÃO ESTIMADA | COD. MUNIC | |
---|---|---|
UF | ||
AC | 906876 | 22 |
AL | 3365351 | 102 |
O nome da segunda coluna não está correto, vamos muda-lo para 'QTD MUNICIPIOS'.
df_uf = df_uf.rename(columns={'COD. MUNIC':'QTD MUNICIPIOS'})
POPULAÇÃO ESTIMADA | QTD MUNICIPIOS | |
---|---|---|
UF | ||
AC | 906876 | 22 |
AL | 3365351 | 102 |
AM | 4269995 | 62 |
AP | 877613 | 16 |
BA | 14985284 | 417 |
CE | 9240580 | 184 |
DF | 3094325 | 1 |
ES | 4108508 | 78 |
GO | 7206589 | 246 |
MA | 7153262 | 217 |
MG | 21411923 | 853 |
MS | 2839188 | 79 |
MT | 3567234 | 141 |
PA | 8777124 | 144 |
PB | 4059905 | 223 |
PE | 9674793 | 185 |
PI | 3289290 | 224 |
PR | 11597484 | 399 |
RJ | 17463349 | 92 |
RN | 3560903 | 167 |
RO | 1815278 | 52 |
RR | 652713 | 15 |
RS | 11466630 | 497 |
SC | 7338473 | 295 |
SE | 2338474 | 75 |
SP | 46649132 | 645 |
TO | 1607363 | 139 |
Por último vamos salvar o DataFrame como um arquivo .csv
df_uf.to_csv('habitantes_e_municipios_por_uf.csv', sep=';', header='True')
Estas são algumas das operações básicas para a exploração de dados que podem ser feitas utilizando a biblioteca Pandas.
Para uma visão mais ampla das capacidades da biblioteca recomendo a documentação oficial: https://pandas.pydata.org/docs/user_guide/index.html#user-guide
Dúvidas ou comentários?
Entre em contato comigo. Veja como me encontrar.