Pular para o conteúdo

    Capítulo 24, Intermediário

    Ler e gravar arrays

    Os dados vêm de arquivos e voltam para arquivos. O NumPy lê e grava texto e tem um formato binário próprio, mais rápido e mais fiel. A escolha entre os dois depende de quem vai ler o resultado.

    Código deste capítulo: intermediario/cap24_ler_gravar.py

    Texto: legível para pessoas e planilhas

    O np.savetxt grava um array como texto, e o np.loadtxt lê de volta. O parâmetro fmt controla a formatação (sem ele, o padrão é uma notação científica enorme), e o header escreve uma linha de títulos:

    intermediario/cap24_ler_gravar.pylinhas 10 a 19
    from pathlib import Path
    
    import numpy as np
    
    dados = np.array([[1.5, 2.0], [3.25, 4.0]])
    np.savetxt("tabela.csv", dados, delimiter=",", header="a,b", comments="", fmt="%.2f")
    print(Path("tabela.csv").read_text(encoding="utf-8"))
    
    lido = np.loadtxt("tabela.csv", delimiter=",", skiprows=1)
    print(np.array_equal(lido, dados))
    
    Saída
    a,b
    1.50,2.00
    3.25,4.00
    
    True
    

    O texto é universal (qualquer planilha abre), mas tem dois custos: perde precisão (o fmt="%.2f" descartou casas) e é lento para arquivos grandes, porque converter texto em número custa.

    Binário: o formato `.npy`

    O np.save grava o array exatamente como está na memória, com o tipo e a forma, em um arquivo .npy. O np.load o devolve idêntico. É rápido, preciso e compacto:

    intermediario/cap24_ler_gravar.pylinhas 24 a 26
    np.save("matriz.npy", dados)
    print(np.array_equal(np.load("matriz.npy"), dados))
    print(Path("matriz.npy").stat().st_size > dados.nbytes)
    
    Saída
    True
    True
    

    O arquivo é só um pouco maior que os dados (nbytes), por causa de um cabeçalho pequeno que descreve o tipo e a forma. Para guardar vários arrays em um arquivo, use o np.savez (ou np.savez_compressed, que comprime), que cria um .npz, uma espécie de pasta de arrays com nome:

    intermediario/cap24_ler_gravar.pylinhas 28 a 30
    np.savez("pacote.npz", x=dados, y=np.arange(3))
    with np.load("pacote.npz") as pacote:
        print(sorted(pacote.files), pacote["y"])
    
    Saída
    ['x', 'y'] [0 1 2]
    
    FormatoQuando usarLimitações
    Texto (.csv, .txt)Trocar dados com planilhas e com outras pessoasLento, perde precisão, só 1D e 2D
    .npyGuardar um array entre execuçõesSó o NumPy lê
    .npzGuardar vários arrays nomeadosSó o NumPy lê
    Parquet, HDF5Conjuntos grandes e tabelas (pandas, capítulo 38)Exigem bibliotecas

    Dados faltando em texto

    O np.genfromtxt lê arquivos de texto com lacunas, e as converte em nan. O loadtxt falharia nesse caso:

    intermediario/cap24_ler_gravar.pylinhas 35 a 37
    Path("faltando.csv").write_text("a,b\n1,2\n3,\n5,6\n", encoding="utf-8")
    cru = np.genfromtxt("faltando.csv", delimiter=",", skip_header=1)
    print(cru)
    
    Saída
    [[ 1.  2.]
     [ 3. nan]
     [ 5.  6.]]
    

    Não carregue pickle de quem você não conhece

    O np.load recusa, por padrão, arrays de objetos Python (allow_pickle=False), e eu mantenho assim. O formato pickle pode executar código arbitrário ao ser lido, e carregar um arquivo de origem desconhecida com ele ligado é um risco de segurança real.

    Quando a tabela tem texto e números misturados

    O NumPy sozinho é desajeitado para tabelas com colunas de tipos diferentes (nomes, datas, números). Para isso existe o pandas (capítulo 38), que lê CSV com pd.read_csv e devolve um DataFrame com rótulos.

    Exercício 1

    Gravar e conferir

    Escreva ida_e_volta(a, pasta) que grave a em pasta/teste.npy, leia de volta e devolva True se o resultado for idêntico (mesmo tipo, mesma forma, mesmos valores).