Pular para o conteúdo

    Capítulo 1, Ambiente

    Por que o Pandas existe

    Listas funcionam até o dia em que os dados ficam reais: faltam valores, aparecem repetições, as colunas misturam tipos. O Pandas existe para esse dia.

    O problema das listas paralelas

    Você já sabe guardar dados em listas do Python. Aqui estão três funcionários do jeito "das listas": uma lista para cada atributo, e a esperança de que elas nunca saiam de sincronia.

    ambiente/cap01_por_que_pandas.pylinhas 10 a 15
    nomes = ["Ana", "Bruno", "Carla"]
    idades = [24, 28, 26]
    salarios = [4500, 6500, 5000]
    
    print(sum(salarios) / len(salarios))
    print([n for n, s in zip(nomes, salarios) if s > 4800])
    
    Saída
    5333.333333333333
    ['Bruno', 'Carla']
    

    Funciona para três pessoas. Agora imagine 500 funcionários, com salários faltando, entradas repetidas e datas misturadas. O primeiro obstáculo é o dado ausente, que as listas não sabem representar:

    ambiente/cap01_por_que_pandas.pylinhas 17 a 21
    salarios_com_falta = [4500, None, 5000]
    try:
        sum(salarios_com_falta)
    except TypeError as erro:
        print("TypeError:", erro)
    
    Saída
    TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
    

    Cada filtro exige um laço escrito à mão, agrupar "por departamento" exige a sua própria lógica de baldes, e nada disso escala além de um exemplo de brinquedo. O Pandas oferece estruturas de dados rápidas e flexíveis para dados estruturados do mundo real, com a mesma relação que uma planilha tem com uma folha de papel com números anotados.

    Os mesmos dados no Pandas

    ambiente/cap01_por_que_pandas.pylinhas 26 a 31
    import pandas as pd
    
    df = pd.DataFrame({"nome": nomes, "idade": idades, "salario": salarios_com_falta})
    print(df)
    print(df["salario"].mean())
    print(df[df["salario"] > 4800])
    
    Saída
        nome  idade  salario
    0    Ana     24   4500.0
    1  Bruno     28      NaN
    2  Carla     26   5000.0
    4750.0
        nome  idade  salario
    2  Carla     26   5000.0
    

    Três coisas aconteceram sem esforço: o dado ausente virou NaN e foi ignorado na média (4750 é a média de 4500 e 5000), as colunas ficaram alinhadas (uma linha é um funcionário completo), e o filtro virou uma expressão, e não um laço.

    Do NumPy ao Pandas

    O Pandas é construído em cima do NumPy. O que ele acrescenta é, em essência, o que uma planilha tem e um array não tem:

    O NumPy dáO Pandas acrescenta
    Arrays numéricos rápidosRótulos nas linhas e nas colunas
    Operações vetorizadasTipos diferentes por coluna
    Layout de memória eficienteTratamento de dados ausentes, limpeza e agrupamento
    ListaArray NumPyDataFrame
    EstruturaContêiner simplesGrade de formato fixo, um só tipoTabela rotulada, linhas e colunas
    TiposQualquer misturaUm tipo para o array inteiroUm tipo por coluna
    RótulosNenhum, só a posiçãoNenhum, só a posiçãoÍndice das linhas e nomes das colunas
    Dados ausentesSem conceitoDifícil de representarNaN nativo
    FiltragemLaços manuaisIndexação booleanaIndexação booleana e query()
    Serve paraDados pequenos e simplesDados numéricos e uniformesPlanilhas e CSVs reais

    A anatomia: `Series` e `DataFrame`

    Uma Series é uma coluna rotulada. Um DataFrame é uma tabela feita de colunas que compartilham o mesmo índice. As peças têm nome:

    ambiente/cap01_por_que_pandas.pylinhas 36 a 40
    print(df.index)
    print(df.columns.tolist())
    print(df.loc[1])
    print(df["salario"])
    print(df.at[0, "nome"])
    
    Saída
    RangeIndex(start=0, stop=3, step=1)
    ['nome', 'idade', 'salario']
    nome       Bruno
    idade         28
    salario      NaN
    Name: 1, dtype: object
    0    4500.0
    1       NaN
    2    5000.0
    Name: salario, dtype: float64
    Ana
    
    PeçaO que éNo exemplo
    ÍndiceO rótulo de cada linha (por padrão, 0, 1, 2...)df.index
    Nomes das colunasO rótulo de cada campodf.columns
    LinhaUm registro completodf.loc[1]
    ColunaUm campo em todos os registros, e é uma Seriesdf["salario"]
    CélulaUm valor, no cruzamento de uma linha e uma colunadf.at[0, "nome"]

    E o NumPy continua ali por baixo. Qualquer coluna numérica entrega o seu array:

    ambiente/cap01_por_que_pandas.pylinhas 42 a 43
    valores = df["salario"].to_numpy()
    print(type(valores).__name__, valores.dtype)
    
    Saída
    ndarray float64
    

    Repare no tipo float64: a coluna de salários era de inteiros, mas o dado ausente (NaN) só existe como decimal, e o Pandas promoveu a coluna inteira. O capítulo 3 explica essa regra.

    O erro de quem vem do NumPy

    Tratar um DataFrame como "um array do NumPy com passos extras". Os velhos hábitos atrapalham: confiar na posição em vez do rótulo, e esquecer que uma coluna pode guardar texto ao lado de uma coluna de números. O Pandas alinha por rótulo, e o capítulo 4 mostra o que isso muda.

    Quando o Pandas não é a resposta

    O Pandas carrega os dados na memória, inteiros. Para conjuntos que passam do tamanho da memória, as saídas são processar em partes (capítulo 32) ou usar ferramentas pensadas para isso, como o DuckDB ou o Polars, que este curso não cobre. Para cálculos puramente numéricos sobre uma grade uniforme, o NumPy continua sendo mais simples e mais rápido.

    Exercício 1

    Lista ou DataFrame?

    Escreva contar_acima(nomes, valores, limite), que devolva quantos valores passam do limite ignorando os ausentes (None), primeiro com uma lista, e depois a mesma conta com uma Series. Confira que as duas dão o mesmo resultado.