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:
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))
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:
np.save("matriz.npy", dados)
print(np.array_equal(np.load("matriz.npy"), dados))
print(Path("matriz.npy").stat().st_size > dados.nbytes)
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:
np.savez("pacote.npz", x=dados, y=np.arange(3))
with np.load("pacote.npz") as pacote:
print(sorted(pacote.files), pacote["y"])
['x', 'y'] [0 1 2]
| Formato | Quando usar | Limitações |
|---|---|---|
Texto (.csv, .txt) | Trocar dados com planilhas e com outras pessoas | Lento, perde precisão, só 1D e 2D |
.npy | Guardar um array entre execuções | Só o NumPy lê |
.npz | Guardar vários arrays nomeados | Só o NumPy lê |
| Parquet, HDF5 | Conjuntos 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:
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)
[[ 1. 2.]
[ 3. nan]
[ 5. 6.]]
Não carregue pickle de quem você não conhece
O
np.loadrecusa, por padrão, arrays de objetos Python (allow_pickle=False), e eu mantenho assim. O formatopicklepode 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_csve devolve umDataFramecom 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).