Saltar al contenido
AZ Tools

Inspector de .npy y .npz de NumPy

Un .npy es una cabecera diminuta seguida de un bloque crudo de números, y esa cabecera es lo único que esta herramienta necesita: seis bytes mágicos, una versión, una longitud y un literal de diccionario de Python —no JSON— con descr, fortran_order y shape. Como solo se lee el primer kilobyte, un array de dos gigabytes se abre tan rápido como uno de dos kilobytes, y ese es justamente el objetivo: saber qué hay dentro antes de decidir si merece la pena cargarlo. El descr se decodifica igual que lo haría numpy. El primer carácter es el orden de bytes —< pequeño, > grande, | no aplicable— y un archivo big-endian se señala, porque leerlo mal no se parece a un error: los números simplemente salen siendo otros números. Después vienen la clase y el tamaño de elemento: booleanos b1, enteros de i1 a i8 y de u1 a u8, flotantes f2, f4 y f8, complejos c8 y c16, cadenas de bytes S<n> que conservan los NUL interiores y pierden los finales, texto U<n> guardado como UCS-4 de modo que tres caracteres ocupan doce bytes, fechas M8 y m8 con su unidad entre corchetes, V<n> crudo, y O, que significa que el array se escribió con allow_pickle=True y que cargarlo ejecuta lo que se hubiera serializado; por eso se avisa de forma destacada y no de pasada. Un dtype estructurado llega como una lista de tuplas (nombre, formato) o (nombre, formato, shape), que además puede anidarse; los desplazamientos no se guardan en ninguna parte, son la suma acumulada de los tamaños, y por eso un dtype alineado lleva campos de relleno sin nombre en su propio descr, que aquí también se muestran con su tramo. El resto son cuentas que puedes comprobar: elementos a partir del shape, bytes esperados como elementos por tamaño de elemento, y los bytes realmente presentes tras la cabecera, de modo que un archivo cortado por una copia fallida o un disco lleno aparece como un déficit y no como una cabecera de aspecto impecable. Los primeros valores se decodifican en la página para los dtypes numéricos simples, en el orden en que están en el archivo, así que un array en orden Fortran se lee columna a columna. Un .npz es un zip de miembros .npy, listados con su dtype, su shape, sus dos tamaños y si están comprimidos. Lo que no hace: deserializar un pickle, decirte qué significan los números ni verificar que los datos sean buenos; informa de lo que el archivo afirma y de dónde esa afirmación choca con su tamaño.

Cómo usar

  1. Suelta un .npy o un .npz en la caja. Solo se lee la cabecera, así que el tamaño del archivo da igual.
  2. Mira primero el dtype, el shape y el número de elementos: es la respuesta a "qué hay aquí dentro" y cuesta una lectura.
  3. Compara Datos esperados con Datos presentes: menos significa archivo truncado, más significa que algo se añadió detrás del array.
  4. Si el dtype es estructurado, abre la tabla de campos: da formato, desplazamiento y tamaño de cada uno, incluido el relleno sin nombre que inserta un dtype alineado.
  5. En un .npz, haz clic en el nombre de un miembro para inspeccionar ese array; la lista muestra lo que ocupa dentro del archivo y ya descomprimido.

Preguntas frecuentes

¿Qué hay exactamente en la cabecera de un .npy?
Seis bytes mágicos (\x93NUMPY), una versión de dos bytes, una longitud de cabecera y luego la cabecera: un literal de diccionario de Python con exactamente tres claves, descr, fortran_order y shape. No es JSON y JSON.parse no puede leerlo: las cadenas usan comillas simples, los booleanos son True y False, el shape es una tupla escrita (3,) y no (3), y hay una coma de más antes de la llave de cierre. El literal se rellena con espacios para que los datos del array empiecen en un múltiplo de 64 bytes, que es lo que permite a numpy mapear el archivo en memoria con cargas alineadas. Aquí se muestra el descr tal cual está escrito, para que puedas compararlo con lo que informa numpy sin dudar si algo se normalizó por el camino.
¿Por qué mi array de cadenas ocupa cuatro veces el número de caracteres?
Porque el dtype U de numpy guarda UCS-4: cada carácter son cuatro bytes fijos, sea cual sea, así que el tamaño de elemento es cuatro veces la longitud declarada. Un campo U32 cuesta 128 bytes por elemento aunque todos los valores sean "ok". Las cadenas de bytes (S) cuestan un byte por carácter pero traen su propia sorpresa: los NUL finales se eliminan al leer, así que un valor que de verdad termine en un byte cero no sobrevive al viaje de ida y vuelta, mientras que los NUL interiores sí se conservan. Si un conjunto de datos pesa mucho más de lo esperado, la causa suele ser un dtype U; lo habitual es guardar el texto como S con una codificación explícita, o llevar las cadenas en un array aparte.
El dtype dice object. ¿Qué significa y por qué el aviso?
Un array de objetos no guarda valores, guarda punteros, así que numpy serializa el conjunto con pickle al guardarlo y lo deserializa al cargarlo. Deserializar no es analizar: puede construir objetos arbitrarios y llamar a código arbitrario, y por eso numpy.load rechaza los arrays de objetos salvo que pases allow_pickle=True. Un .npy cuyo descr es |O es tanto un programa como un dato, y su seguridad es la de quien lo escribió. Esta página no deserializa nada: informa del dtype y del tamaño del pickle y ahí se detiene. Si el array contiene cadenas normales o listas irregulares, casi siempre conviene reescribirlo con un dtype de ancho fijo o como varios arrays dentro de un .npz.
¿Qué cambia realmente fortran_order?
Solo el orden en que los mismos números aparecen en el archivo. Con fortran_order en False el último eje varía más rápido (C, por filas); con True lo hace el primero (por columnas), que es lo que se obtiene de np.asfortranarray o de datos que vienen de Fortran o MATLAB. El shape y el dtype son idénticos en ambos casos, así que un lector que ignore la bandera no falla: transpone el array en silencio, que es bastante peor que una excepción. La vista previa sigue el archivo y no la disposición lógica, así que un array 2×3 en orden Fortran se lee como columna 0, columna 1 y columna 2.
¿Por qué hay tres versiones del formato?
La 1.0 guarda la longitud de cabecera en dos bytes, lo que la limita a 65535 bytes. La 2.0 usa cuatro bytes para el caso raro en que eso no basta: un dtype estructurado con unos miles de campos lo consigue, y la cabecera puede ocupar cientos de kilobytes. La 3.0 es igual que la 2.0 pero declara la cabecera como UTF-8 en vez de Latin-1, y numpy solo la escribe cuando un nombre de campo tiene un carácter que Latin-1 no admite. numpy siempre elige la versión más baja que quepa, así que un archivo 2.0 o 3.0 ya dice algo por sí mismo. Conviene saber además que el propio cargador de numpy rechaza cualquier cabecera de más de 10000 bytes salvo que se suba max_header_size, de modo que un archivo 2.0 legítimo puede no abrirse en Python y sí aquí.
¿Se sube el archivo? ¿Puede abrir un array enorme?
No se sube nada, y sí. El archivo se lee con la File API del navegador y solo por trozos: el primer kilobyte para la cabecera y unos kilobytes más para la vista de valores. Un array de 2 GB nunca se mantiene en memoria, que es exactamente el caso para el que existe esto: alguien te pasa un archivo, quieres su dtype y su shape, y cargarlo costaría minutos y casi toda la RAM. En un .npz solo se leen el directorio zip del final del archivo y la cabecera de cada miembro, así que listar un archivo son dos lecturas más una por miembro. No se envía nada: puedes desconectar la red después de cargar la página y sigue funcionando.

Herramientas relacionadas