Pular para o conteúdo

    Capítulo 35, Avançado

    O que mudou no Pandas 3

    Muito do que se encontra na internet foi escrito para o Pandas 1 ou 2. Este capítulo reúne, em um lugar, o que mudou, **cada item provado por código**, e como encontrar o que precisa ser migrado em um projeto antigo.

    O que foi removido

    Estes nomes, muito comuns em material antigo, não existem mais no Pandas 3:

    avancado/cap35_pandas3.pylinhas 10 a 25
    import inspect
    
    import pandas as pd
    
    removidos = {
        "DataFrame.append": hasattr(pd.DataFrame, "append"),
        "DataFrame.applymap": hasattr(pd.DataFrame, "applymap"),
        "DataFrame.iteritems": hasattr(pd.DataFrame, "iteritems"),
        "DataFrame.mad": hasattr(pd.DataFrame, "mad"),
        "DataFrame.ix": hasattr(pd.DataFrame, "ix"),
        "DataFrame.swapaxes": hasattr(pd.DataFrame, "swapaxes"),
        "DataFrame.first": hasattr(pd.DataFrame, "first"),
        "Series.view": hasattr(pd.Series, "view"),
    }
    print(removidos)
    print("o substituto do applymap:", hasattr(pd.DataFrame, "map"))
    
    Saída
    {'DataFrame.append': False, 'DataFrame.applymap': False, 'DataFrame.iteritems': False, 'DataFrame.mad': False, 'DataFrame.ix': False, 'DataFrame.swapaxes': False, 'DataFrame.first': False, 'Series.view': False}
    o substituto do applymap: True
    
    AntesAgora
    df.append(outra)pd.concat([df, outra])
    df.applymap(f)df.map(f)
    df.iteritems()df.items()
    df.ix[...]df.loc[...] ou df.iloc[...]
    df.first("5D")Fatiar pelo índice de datas
    s.view(...)s.astype(...) ou o NumPy

    O que mudou de comportamento

    As mudanças mais perigosas não dão erro: o mesmo código produz outro resultado. Cada uma foi vista em um capítulo deste curso, e aqui estão juntas, com a prova:

    avancado/cap35_pandas3.pylinhas 30 a 48
    import warnings
    
    import numpy as np
    
    texto = pd.Series(["a", "b"])
    print("1. texto é str:", texto.dtype)
    
    s = pd.Series([1, 2, 3], index=["x", "y", "z"])
    try:
        s[0]
    except KeyError:
        print("2. s[0] em índice de texto: KeyError (use .iloc[0])")
    
    grupos = inspect.signature(pd.DataFrame.groupby).parameters
    print("3. groupby observed:", grupos["observed"].default, "| tem axis:", "axis" in grupos)
    
    print("4. resolução de datas:", pd.to_datetime(pd.Series(["2025-01-01"])).dtype)
    
    print("5. str.contains com ausente:", pd.Series(["a", None]).str.contains("a").tolist())
    
    Saída
    1. texto é str: str
    2. s[0] em índice de texto: KeyError (use .iloc[0])
    3. groupby observed: True | tem axis: False
    4. resolução de datas: datetime64[us]
    5. str.contains com ausente: [True, False]
    
    avancado/cap35_pandas3.pylinhas 50 a 64
    try:
        pd.Series([1.0, None]).fillna(method="ffill")
    except TypeError:
        print("6. fillna(method=...) removido: use ffill() e bfill()")
    
    try:
        pd.Series([1, 2]).resample("M")
    except (TypeError, ValueError):
        print("7. resample('M') não vale mais: use 'ME' ou 'MS'")
    
    df = pd.DataFrame({"a": [1, 2, 3], "b": [10.0, 20.0, 30.0]})
    with warnings.catch_warnings(record=True) as avisos:
        warnings.simplefilter("always")
        df[df["a"] > 1]["b"] = 0
    print("8. atribuição encadeada:", [a.category.__name__ for a in avisos], df["b"].tolist())
    
    Saída
    6. fillna(method=...) removido: use ffill() e bfill()
    7. resample('M') não vale mais: use 'ME' ou 'MS'
    8. atribuição encadeada: ['ChainedAssignmentError'] [10.0, 20.0, 30.0]
    
    #Antes (Pandas 1 e 2)Pandas 3Capítulo
    1Texto era objectTexto é str3
    2s[0] caía para a posição em índice de textoKeyError4
    3groupby(observed=False) por padrão, com axisobserved=True, sem axis19 e 23
    4Datas em nanossegundosDatas em microssegundos (datetime64[us])18
    5str.contains devolvia NaN para ausentes (em object)Devolve False no tipo str8
    6fillna(method="ffill")Removido: ffill()9
    7resample("M")"ME" ou "MS"18
    8Atribuição encadeada às vezes funcionavaNunca altera o original27
    9groupby().apply recebia a coluna do grupoNão recebe mais23
    10to_numpy() devolvia um array gravável (às vezes)Visão somente leitura27

    Como achar o que migrar

    Um projeto antigo pode ter centenas de usos de padrões que mudaram. O ruff, com as regras do conjunto PD (pandas-vet), aponta alguns dos mais comuns. Considere este arquivo antigo:

    velho.py
    import pandas as pd
    
    df = pd.read_csv("x.csv")
    df.dropna(inplace=True)
    valores = df.values
    df = df.append({"a": 1}, ignore_index=True)
    antigo = df.iteritems()
    
    Terminal
    ruff check --select PD velho.py
    
    Saída
    velho.py:4:11: PD002 `inplace=True` should be avoided; it has inconsistent behavior
    velho.py:5:11: PD011 Use `.to_numpy()` or `.array` instead of `.values`
    

    O ruff apontou dois problemas, e eu conferi que ele não aponta o append nem o iteritems: esses já falham sozinhos, na primeira execução, com um AttributeError. A ferramenta ajuda, mas não substitui os testes: a melhor rede de segurança em uma migração é uma boa suíte, rodada com o Pandas 3.

    Dá para fazer uma varredura simples também, com uma função que procura os padrões antigos no texto do código:

    avancado/cap35_pandas3.pylinhas 69 a 93
    import re
    
    PADROES = {
        r"\.append\(": "df.append foi removido (use pd.concat)",
        r"\.applymap\(": "applymap foi substituído por map",
        r"inplace\s*=\s*True": "evite inplace",
        r"fillna\(\s*method\s*=": "fillna(method=) foi removido (use ffill/bfill)",
        r"\.ix\[": ".ix foi removido (use loc ou iloc)",
        r"resample\(\s*[\"']M[\"']": "'M' virou 'ME' ou 'MS'",
        r"\]\[[^\]]+\]\s*=": "possível atribuição encadeada (use .loc)",
    }
    
    
    def auditar(codigo: str) -> list[str]:
        achados = []
        for numero, linha in enumerate(codigo.splitlines(), start=1):
            for padrao, aviso in PADROES.items():
                if re.search(padrao, linha):
                    achados.append(f"linha {numero}: {aviso}")
        return achados
    
    
    amostra = 'df = df.append(x)\ndf["a"].fillna(0, inplace=True)\nr = s.resample("M").sum()\n'
    for achado in auditar(amostra):
        print(achado)
    
    Saída
    linha 1: df.append foi removido (use pd.concat)
    linha 2: evite inplace
    linha 3: 'M' virou 'ME' ou 'MS'
    

    Fixar a versão enquanto você migra

    Se um projeto ainda não pode migrar, fixe a versão, e migre quando houver tempo e testes:

    Terminal
    uv add "pandas<3"
    

    A regra de ouro: migre com testes. As mudanças de comportamento (itens 1 a 10 da tabela) não levantam erro, e só uma boa suíte, ou uma conferência cuidadosa das saídas, mostra que algo mudou.

    Exercício 1

    O auditor de código

    Use a função auditar para conferir que ela acha os três problemas da amostra, em linhas diferentes, e que não acusa um código já migrado (pd.concat, ffill, resample("ME")).