# Importar a biblioteca pandas
import pandas as pd
# Importação usando pandas para ler CSV da web
penguins = pd.read_csv('https://raw.githubusercontent.com/allisonhorst/palmerpenguins/master/inst/extdata/penguins.csv')
# Primeira inspeção sempre com glimpse()
glimpse(penguins)Fundamentos da AED e Tidy Data e Importação
A Análise Exploratória de Dados (AED) é uma das etapas mais importantes e fascinantes da ciência de dados. É o momento em que você mergulha profundamente nos dados, descobrindo padrões, identificando anomalias e gerando insights que orientarão suas próximas análises. Mas para que essa exploração seja eficaz, os dados precisam estar organizados de forma consistente e estruturada.
É aqui que entra o conceito de Tidy Data (Dados Organizados), um paradigma fundamental que revolucionou a forma como manipulamos e analisamos dados. Quando seus dados seguem os princípios de tidy data, cada operação de transformação, visualização e modelagem se torna mais intuitiva e eficiente.
A AED é exploratória - você está investigando e descobrindo, diferentemente da análise confirmatória que testa hipóteses específicas. Este módulo estabelece as bases sólidas para sua jornada na análise exploratória de dados, apresentando os conceitos fundamentais e as técnicas essenciais para trabalhar com dados organizados.
Importação de Dados na Prática: Case dos Pinguins de Palmer
Importando Nosso Dataset: Pinguins de Palmer
Vamos usar como case prático o dataset dos Pinguins de Palmer - dados reais coletados na Estação Palmer, Antártica (2007-2009) sobre três espécies de pinguins: Adelie, Chinstrap e Gentoo.
Nossos dados estão armazenados no formato CSV (Comma-Separated Values) em um repositório online. Para lê-los e trabalhar com eles em Python, vamos utilizar a biblioteca pandas, que é a ferramenta padrão para manipulação de dados tabulares.
Rows: 344
Columns: 8
> species object 0 (0%) NAs : Adelie, Adelie, Adelie, Adelie, Adelie
> island object 0 (0%) NAs : Torgersen, Torgersen, Torgersen, Torgersen, Torgersen
> bill_length_mm float64 2 (1%) NAs : 39.1, 39.5, 40.3, nan, 36.7
> bill_depth_mm float64 2 (1%) NAs : 18.7, 17.4, 18.0, nan, 19.3
> flipper_length_mm float64 2 (1%) NAs : 181.0, 186.0, 195.0, nan, 193.0
> body_mass_g float64 2 (1%) NAs : 3750.0, 3800.0, 3250.0, nan, 3450.0
> sex object 11 (3%) NAs : male, female, female, nan, female
> year int64 0 (0%) NAs : 2007, 2007, 2007, 2007, 2007
Resultado: 344 pinguins observados com 8 características cada um, incluindo dimensões do bico, massa corporal, ilha de origem e sexo.
glimpse()
Nossa ferramenta diagnóstica principal será a função glimpse() - uma função customizada que criamos pois não existe equivalente nos pacotes padrão do Python. Cada linha mostra:
- Nome da variável (ex:
species) - Tipo de dados (object = categórica, float64 = numérica)
- Valores ausentes (quantidade e percentual)
- Amostra dos dados (primeiros 5 valores)
Esta visão única permite diagnosticar rapidamente estrutura, qualidade, tipos e conteúdo dos dados.
Trabalhando com Diferentes Formatos de Arquivo
Nosso dataset dos pinguins está disponível em CSV, mas vamos ver como trabalharíamos com outros formatos comuns:
CSV - O Mais Comum (Nosso Case)
Já importamos nossos pinguins de CSV! Vamos ver uma amostra e outras opções do CSV:
# Nossa amostra dos pinguins em CSV
penguins_sample = penguins.head(5)
penguins_sample| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year | |
|---|---|---|---|---|---|---|---|---|
| 0 | Adelie | Torgersen | 39.1 | 18.7 | 181.0 | 3750.0 | male | 2007 |
| 1 | Adelie | Torgersen | 39.5 | 17.4 | 186.0 | 3800.0 | female | 2007 |
| 2 | Adelie | Torgersen | 40.3 | 18.0 | 195.0 | 3250.0 | female | 2007 |
| 3 | Adelie | Torgersen | NaN | NaN | NaN | NaN | NaN | 2007 |
| 4 | Adelie | Torgersen | 36.7 | 19.3 | 193.0 | 3450.0 | female | 2007 |
JSON - APIs e Web
Se os pinguins estivessem em JSON:
# Importar de JSON (estrutura comum em APIs)
penguins_json = pd.read_json('pinguins_palmer.json')
# Ou de uma API fictícia
response = requests.get('https://api.palmer-station.org/penguins')
penguins_api = pd.DataFrame(response.json()['data'])Excel - Ambiente Corporativo
Se recebêssemos os pinguins em Excel:
# Aba específica dos dados
penguins_xl = pd.read_excel('palmer_penguins.xlsx', sheet_name='RawData')
# Múltiplas abas (ex: uma por espécie)
adelie = pd.read_excel('penguins_by_species.xlsx', sheet_name='Adelie')
chinstrap = pd.read_excel('penguins_by_species.xlsx', sheet_name='Chinstrap')
gentoo = pd.read_excel('penguins_by_species.xlsx', sheet_name='Gentoo')Tidy Data: O Fundamento da Organização de Dados
Os Três Princípios Fundamentais
O conceito de Tidy Data, desenvolvido por Hadley Wickham, define três princípios fundamentais para organização de dados:
- Cada variável forma uma coluna: Cada coluna representa uma única variável medida
- Cada observação forma uma linha: Cada linha representa uma única unidade observacional
- Cada célula contém um único valor: Cada intersecção linha-coluna contém apenas um valor

Nossos Dados São Tidy?
glimpse(penguins)Rows: 344
Columns: 8
> species object 0 (0%) NAs : Adelie, Adelie, Adelie, Adelie, Adelie
> island object 0 (0%) NAs : Torgersen, Torgersen, Torgersen, Torgersen, Torgersen
> bill_length_mm float64 2 (1%) NAs : 39.1, 39.5, 40.3, nan, 36.7
> bill_depth_mm float64 2 (1%) NAs : 18.7, 17.4, 18.0, nan, 19.3
> flipper_length_mm float64 2 (1%) NAs : 181.0, 186.0, 195.0, nan, 193.0
> body_mass_g float64 2 (1%) NAs : 3750.0, 3800.0, 3250.0, nan, 3450.0
> sex object 11 (3%) NAs : male, female, female, nan, female
> year int64 0 (0%) NAs : 2007, 2007, 2007, 2007, 2007
Verificação dos Princípios
| Princípio | Status | Verificação | Exemplos |
|---|---|---|---|
| 1. Cada coluna = uma variável | ✅ SIM | Todas as colunas representam variáveis distintas | • species (categórica)• bill_length_mm (numérica)• island (categórica)• sex (categórica) |
| 2. Cada linha = uma observação | ✅ SIM | Cada linha representa um pinguim individual | • Linha 1 = Pinguim #1 • Linha 2 = Pinguim #2 • Linha 344 = Pinguim #344 |
| 3. Cada célula = um valor único | ⚠️ QUASE | Temos alguns NAs, mas sem múltiplos valores | • Células contêm apenas um valor • Ou contêm NA (ausente) • Sem valores concatenados |
🎯 Conclusão: Nossos dados estão praticamente tidy! Só precisamos tratar os valores ausentes (próximo módulo).
Exemplos de Violação dos Princípios
Para entender melhor os princípios de tidy data, vamos ver o que acontece quando eles não são seguidos. Criamos versões problemáticas dos nossos dados de pinguins para demonstrar problemas comuns encontrados na prática:
Os códigos dos problemas a seguir não podem ser executados diretamente pois as variáveis não foram definidas explicitamente. Eles servem apenas para ilustrar como dados “untidy” se parecem e quais problemas causam. O foco está na compreensão visual dos problemas, não na execução do código.
Problema 1: Wide Format Inadequado
glimpse(penguins_wide)Rows: 3
Columns: 7
> island object 0 (0%) NAs : Biscoe, Dream, Torgersen
> bill_depth_mm_Adelie float64 0 (0%) NAs : 18.4, 18.2, 18.5
> bill_depth_mm_Chinstrap float64 2 (67%) NAs : nan, 18.4, nan
> bill_depth_mm_Gentoo float64 2 (67%) NAs : 15.0, nan, nan
> bill_length_mm_Adelie float64 0 (0%) NAs : 39.0, 38.5, 39.0
> bill_length_mm_Chinstrap float64 2 (67%) NAs : nan, 48.8, nan
> bill_length_mm_Gentoo float64 2 (67%) NAs : 47.6, nan, nan
As espécies (Adelie, Chinstrap, Gentoo) viraram colunas, violando o princípio “cada coluna é uma variável”.
Espécie deveria ser uma variável categórica em uma única coluna, não espalhada em múltiplas colunas.
Correção aplicada: Wide → Long usando melt()
# Transformação: Wide → Long usando melt()
penguins_fixed_1 = pd.melt(
penguins_wide,
id_vars=['island'],
var_name='measure_species',
value_name='value'
)
# Separar medida e espécie
penguins_fixed_1[['measure', 'species']] = penguins_fixed_1['measure_species'].str.split('_', expand=True, n=1)
penguins_fixed_1 = penguins_fixed_1.drop('measure_species', axis=1)
# Verificar resultado
glimpse(penguins_fixed_1, maxlen=80)Rows: 18
Columns: 4
> island object 0 (0%) NAs : Biscoe, Dream, Torgersen, Biscoe, Dream
> value float64 8 (44%) NAs : 18.4, 18.2, 18.5, nan, 18.4
> measure object 0 (0%) NAs : bill, bill, bill, bill, bill
> species object 0 (0%) NAs : depth_mm_Adelie, depth_mm_Adelie, depth_mm_Adeli...
Problema 2: Múltiplas Variáveis por Coluna
glimpse(penguins_collapsed)Rows: 8
Columns: 5
> species object 0 (0%) NAs : Adelie, Adelie, Adelie, Adelie, Adelie
> island object 0 (0%) NAs : Torgersen, Torgersen, Torgersen, Torgersen, Torgersen
> sex object 0 (0%) NAs : male, female, female, female, male
> bill_dimensions object 0 (0%) NAs : 39.1,18.7, 39.5,17.4, 40.3,18.0, 36.7,19.3, 39.3,20.6
> physical_measures object 0 (0%) NAs : 181.0,3750.0, 186.0,3800.0, 195.0,3250.0, 193.0,3450.0, 190....
Cada célula da coluna bill_dimensions contém dois valores (comprimento,profundidade) separados por vírgula.
Isso viola o princípio “cada célula contém um único valor”. Cada medida deveria ter sua própria coluna.
Correção aplicada: Separando valores concatenados usando str.split()
# Separar bill_dimensions em duas colunas
penguins_fixed_2 = penguins_collapsed.copy()
penguins_fixed_2[['bill_length_mm', 'bill_depth_mm']] = penguins_collapsed['bill_dimensions'].str.split(',', expand=True)
penguins_fixed_2[['flipper_length_mm', 'body_mass_g']] = penguins_collapsed['physical_measures'].str.split(',', expand=True)
# Converter para numeric
for col in ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']:
penguins_fixed_2[col] = pd.to_numeric(penguins_fixed_2[col])
# Remover colunas problemáticas
penguins_fixed_2 = penguins_fixed_2.drop(['bill_dimensions', 'physical_measures'], axis=1)
# Verificar resultado
glimpse(penguins_fixed_2, maxlen=80)Rows: 8
Columns: 7
> species object 0 (0%) NAs : Adelie, Adelie, Adelie, Adelie, Adelie
> island object 0 (0%) NAs : Torgersen, Torgersen, Torgersen, Torger...
> sex object 0 (0%) NAs : male, female, female, female, male
> bill_length_mm float64 0 (0%) NAs : 39.1, 39.5, 40.3, 36.7, 39.3
> bill_depth_mm float64 0 (0%) NAs : 18.7, 17.4, 18.0, 19.3, 20.6
> flipper_length_mm float64 0 (0%) NAs : 181.0, 186.0, 195.0, 193.0, 190.0
> body_mass_g float64 0 (0%) NAs : 3750.0, 3800.0, 3250.0, 3450.0, 3650.0
Problema 3: Long Format Excessivo
glimpse(penguins_too_long)Rows: 24
Columns: 6
> penguin_id int64 0 (0%) NAs : 1, 1, 1, 1, 2
> species object 0 (0%) NAs : Adelie, Adelie, Adelie, Adelie, Adelie
> island object 0 (0%) NAs : Torgersen, Torgersen, Torgersen, Torgersen, Torgersen
> sex object 0 (0%) NAs : male, male, male, male, female
> measurement_type object 0 (0%) NAs : bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass...
> measurement_value float64 0 (0%) NAs : 39.1, 18.7, 181.0, 3750.0, 186.0
Cada pinguim (que deveria ser uma linha) agora está espalhado em 4 linhas.
Isso viola o princípio “cada observação forma uma linha”. Um pinguim individual deveria ocupar apenas uma linha.
Correção aplicada: Long → Wide usando pivot_table()
# Transformação: Long → Wide usando pivot
penguins_fixed_3 = penguins_too_long.pivot_table(
index=['species', 'island', 'sex'],
columns='measurement_type',
values='measurement_value',
aggfunc='first'
).reset_index()
# Limpar nome das colunas
penguins_fixed_3.columns.name = None
# Verificar resultado
glimpse(penguins_fixed_3, maxlen=80)Rows: 2
Columns: 7
> species object 0 (0%) NAs : Adelie, Adelie
> island object 0 (0%) NAs : Torgersen, Torgersen
> sex object 0 (0%) NAs : female, male
> bill_depth_mm float64 0 (0%) NAs : 17.4, 18.7
> bill_length_mm float64 0 (0%) NAs : 39.5, 39.1
> body_mass_g float64 0 (0%) NAs : 3800.0, 3750.0
> flipper_length_mm float64 0 (0%) NAs : 186.0, 181.0
Problema 4: Headers Como Dados
glimpse(penguins_years_as_cols)Rows: 3
Columns: 4
> species object 0 (0%) NAs : Adelie, Chinstrap, Gentoo
> 2007 float64 0 (0%) NAs : 38.9, 48.7, 47.1
> 2008 float64 0 (0%) NAs : 38.6, 48.7, 47.0
> 2009 float64 0 (0%) NAs : 39.0, 49.1, 48.6
Os anos (2007, 2008, 2009) viraram colunas em vez de serem uma variável temporal.
Ano deveria ser uma coluna categórica/temporal única, não espalhado em múltiplas colunas.
Correção aplicada: Transformando headers em uma variável usando melt()
# Transformação: Headers → Variável usando melt()
penguins_fixed_4 = pd.melt(
penguins_years_as_cols,
id_vars=['species'],
var_name='year',
value_name='avg_bill_length'
)
# Converter ano para inteiro
penguins_fixed_4['year'] = penguins_fixed_4['year'].astype(int)
# Verificar resultado
glimpse(penguins_fixed_4, maxlen=80)Rows: 9
Columns: 3
> species object 0 (0%) NAs : Adelie, Chinstrap, Gentoo, Adelie, Chinst...
> year int64 0 (0%) NAs : 2007, 2007, 2007, 2008, 2008
> avg_bill_length float64 0 (0%) NAs : 38.9, 48.7, 47.1, 38.6, 48.7
Por que Tidy Data Facilita a Análise?
Com dados tidy, operações de análise se tornam naturais e intuitivas. Veja alguns exemplos:
1. Média do comprimento do bico por espécie:
especies_media = penguins.groupby('species')['bill_length_mm'].mean().round(1)
especies_mediaspecies
Adelie 38.8
Chinstrap 48.8
Gentoo 47.5
Name: bill_length_mm, dtype: float64
2. Contagem por ilha:
ilha_contagem = penguins['island'].value_counts()
ilha_contagemisland
Biscoe 168
Dream 124
Torgersen 52
Name: count, dtype: int64
3. Estatísticas por sexo:
sexo_stats = penguins.groupby('sex')['body_mass_g'].agg(['mean', 'std']).round(0)
sexo_stats| mean | std | |
|---|---|---|
| sex | ||
| female | 3862.0 | 666.0 |
| male | 4546.0 | 788.0 |
💡 Note como as operações fluem naturalmente com dados tidy! Cada análise usa diretamente as colunas como variáveis, sem necessidade de transformações complexas.
Conclusão
Neste módulo, estabelecemos as bases fundamentais para análise exploratória de dados:
- Função
glimpse(): Nossa ferramenta diagnóstica principal - Princípios Tidy Data: Cada coluna = variável, cada linha = observação
- Identificação de problemas: Wide format, múltiplas variáveis por célula, etc.
- Transformações básicas:
melt(),pivot(),str.split() - Boas práticas: Sempre inspecionar primeiro, detectar problemas comuns
Os dados dos pinguins estão agora organizados e compreendidos. No próximo módulo, resolveremos o problema dos valores ausentes e aplicaremos técnicas de limpeza e transformação que completarão nossa preparação para análise estatística.
Continuaremos com os mesmos pinguins, mas focaremos em:
- Estratégias para valores ausentes
- Detecção e tratamento de outliers
- Validação da qualidade após limpeza
- Preparação final para análise estatística
Recursos Adicionais
- Tidy Data (Hadley Wickham) - Paper original sobre tidy data
- Pandas Documentation - Documentação completa do pandas
- Python Data Science Handbook - Guia abrangente
Implementação da Função glimpse()
glimpse() (clique para expandir)
A implementação da função glimpse() é um pouco complexa, mas o resultado é extremamente útil para diagnóstico rápido de dados. Como não existe uma função equivalente nos pacotes padrão do Python, criamos esta versão customizada que você pode reutilizar em seus projetos.
def glimpse(df, maxlen=100):
"""
Imprime um resumo conciso de um DataFrame.
Args:
df: O DataFrame a resumir.
maxlen: Comprimento máximo das linhas de saída.
"""
print("Rows:", df.shape[0])
print("Columns:", df.shape[1])
def pad(y):
max_pad_len = max([len(x) for x in y])
return [x.ljust(max_pad_len) for x in y]
# Column Name with size limiter
max_col_len = 30
col_names = [
(
str(col)
if len(str(col)) <= max_col_len
else str(col)[: max_col_len - 3] + "..."
)
for col in df.columns.tolist()
]
toprnt = pad(col_names)
# Column Type
toprnt = pad(
[toprnt[i] + " " + str(df.iloc[:, i].dtype) for i in range(df.shape[1])]
)
# Num NAs
num_nas = [df.iloc[:, i].isnull().sum() for i in range(df.shape[1])]
num_nas_ratio = [int(round(x * 100 / df.shape[0])) for x in num_nas]
num_nas_str = [
str(x) + " (" + str(y) + "%)"
for x, y in zip(num_nas, num_nas_ratio, strict=False)
]
nas_max_len = max([len(x) for x in num_nas_str])
num_nas_str = [x.rjust(nas_max_len) for x in num_nas_str]
toprnt = [x + " " + y + " NAs" for x, y in zip(toprnt, num_nas_str, strict=False)]
# Separator
toprnt = ["> " + x + " : " for x in toprnt]
# Values
toprnt = [
toprnt[i] + ", ".join([str(y) for y in df.iloc[:, i].head(5)])
for i in range(df.shape[1])
]
# Trim to maxlen
toprnt = [x[: min(maxlen, len(x))] + "..." if len(x) > maxlen else x for x in toprnt]
for x in toprnt:
print(x)Por que não usar apenas df.info() ou df.describe()?
A função glimpse() combina várias informações em uma visão compacta: - Formato dos dados (linhas × colunas) - Tipos de cada coluna - Contagem e percentual de valores ausentes - Amostra dos valores reais de cada coluna
É especialmente útil para datasets com muitas colunas, onde df.head() mostra pouco e df.info() é muito verboso.
💡 Dica: Copie esta função e mantenha-a num arquivo utils.py para reutilizar em todos os seus projetos de análise de dados!