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.

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

Dica🔍 Entendendo o 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:

ImportantePrincípios do Tidy Data
  1. Cada variável forma uma coluna: Cada coluna representa uma única variável medida
  2. Cada observação forma uma linha: Cada linha representa uma única unidade observacional
  3. Cada célula contém um único valor: Cada intersecção linha-coluna contém apenas um valor

Representação visual dos princípios de Tidy Data

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:

NotaApenas para Demonstração

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
AvisoO que está errado?

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....
AvisoO que está errado?

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
AvisoO que está errado?

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
AvisoO que está errado?

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_media
species
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_contagem
island
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:

DicaO que Aprendemos
  • 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.

NotaPróximo Módulo: Transformação e Limpeza

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


Implementação da Função glimpse()

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!