bonitos ndarrays de impresión con caracteres Unicode

Recientemente me he dado cuenta de que la funcionalidad de impresión de Python no es consistente para NumPy ndarays. Por ejemplo, imprime una matriz horizontal 1D horizontalmente:

import numpy as np A1=np.array([1,2,3]) print(A1) #--> [1 2 3] 

pero una matriz horizontal 1D con paréntesis redundantes verticalmente:

 A2=np.array([[1],[2],[3]]) print(A2) #--> [[1] # [2] # [3]] 

una matriz vertical 1D horizontalmente:

 A3=np.array([[1,2,3]]) print(A3) #--> [[1 2 3]] 

y una matriz 2D:

 B=np.array([[11,12,13],[21,22,23],[31,32,32]]) print(B) # --> [[11 12 13] # [21 22 23] # [31 32 32]] 

donde la primera dimensión es ahora vertical. Se pone aún peor para las dimensiones más altas, ya que todas se imprimen verticalmente:

 C=np.array([[[111,112],[121,122]],[[211,212],[221,222]]]) print(C) #--> [[[111 112] # [121 122]] # # [[211 212] # [221 222]]] 

Un comportamiento coherente en mi opinión sería imprimir las dimensiones pares horizontalmente y las irregulares verticalmente. Usando caracteres Unicode sería posible formatearlo muy bien. Me preguntaba si es posible crear una función para imprimir arrays de arriba como:

 A1 --> [1 2 3] A2 --> ┌┌─┐┌─┐┌─┐┐ │ 1 2 3 │ └└─┘└─┘└─┘┘ A3 --> ┌┌─┐┐ # \u250c\u2500\u2510 │ 1 │ # \u2502 │ 2 │ │ 3 │ └└─┘┘ # \u2514\u2500\u2518 B --> ┌┌──┐┌──┐┌──┐┐ │ 11 21 31 │ │ 12 22 32 │ │ 13 23 33 │ └└──┘└──┘└──┘┘ C --> ┌┌─────────┐┌─────────┐┐ │ [111 112] [211 212] │ │ [121 122] [221 222] │ └└─────────┘└─────────┘┘ 

Encontré esta esencia que se ocupa de los diferentes números de dígitos. Intenté crear un prototipo de una función recursiva para implementar el concepto anterior:

  def npprint(A): assert isinstance(A, np.ndarray), "input of npprint must be array like" if A.ndim==1 : print(A) else: for i in range(A.shape[1]): npprint(A[:,i]) 

Funciona un poco para A1 , A2 , A3 y B pero no para C Le agradecería que me ayudara a saber cómo debería ser npprint para lograr una salida superior para ndarrays de dimensiones arbitrarias.

PS1. En el entorno de Jupyter, se puede utilizar LaTeX \mathtools \underbracket y \overbracket en Markdown. La bonita funcionalidad de impresión de Sympy también es un excelente punto de partida. Puede usar ASCII, Unicode, LaTeX …

PS2. Me han dicho que, efectivamente, hay una consistencia en la forma en que se imprimen los ndarrays. Sin embargo, en mi humilde opinión es un poco cableado y no intuitivo. Tener una función de impresión bastante flexible podría ayudar mucho a mostrar ndarrays en diferentes formas.

PS3. Los chicos de Sympy ya han considerado los dos puntos que he mencionado aquí. su módulo Matrix es bastante consistente ( A1 y A2 son iguales) y también tienen una función de pprint que hace lo mismo y espero de npprint aquí.

Fue una gran revelación para mí comprender que las matrices numpy no son nada como matrices MATLAB o matrices multidimensionales que tenía en mente. Son listas de Python anidadas bastante homogéneas y uniformes. También entendí que la primera dimensión de una matriz numpy es el par de corchetes más profundo / interno que se imprime horizontalmente y luego desde allí se imprime la segunda dimensión verticalmente, Tercera verticalmente con una línea espaciada …

De todas formas, tener una función ppring (inspirada en la convención de nomenclatura de Sympy) podría ayudar mucho. así que pondré una implementación muy mala aquí con la esperanza de que inspire a otros Pythoners avanzados a encontrar mejores soluciones:

 def pprint(A): if A.ndim==1: print(A) else: w = max([len(str(s)) for s in A]) print(u'\u250c'+u'\u2500'*w+u'\u2510') for AA in A: print(' ', end='') print('[', end='') for i,AAA in enumerate(AA[:-1]): w1=max([len(str(s)) for s in A[:,i]]) print(str(AAA)+' '*(w1-len(str(AAA))+1),end='') w1=max([len(str(s)) for s in A[:,-1]]) print(str(AA[-1])+' '*(w1-len(str(AA[-1]))),end='') print(']') print(u'\u2514'+u'\u2500'*w+u'\u2518') 

y el resultado es algo aceptable para arreglos 1D y 2D:

 B1=np.array([[111,122,133],[21,22,23],[31,32,33]]) pprint(B1) #┌─────────────┐ # [111 122 133] # [21 22 23 ] # [31 32 33 ] #└─────────────┘ 

Este es de hecho un código muy malo, solo funciona para enteros. Esperemos que otros encuentren mejores soluciones.

PS1. Eric Wieser ya ha implementado un prototipo HTML muy bueno para IPython / Jupiter que puede verse aquí :

introduzca la descripción de la imagen aquí

Puede seguir la discusión en la lista de correo numpy aquí .

PS2. También publiqué esta idea aquí en Reddit .

PS3 pasé un tiempo para extender el código a matrices tridimensionales 3D:

 def ndtotext(A, w=None, h=None): if A.ndim==1: if w == None : return str(A) else: s= '[' for i,AA in enumerate(A[:-1]): s += str(AA)+' '*(max(w[i],len(str(AA)))-len(str(AA))+1) s += str(A[-1])+' '*(max(w[-1],len(str(A[-1])))-len(str(A[-1]))) +'] ' elif A.ndim==2: w1 = [max([len(str(s)) for s in A[:,i]]) for i in range(A.shape[1])] w0 = sum(w1)+len(w1)+1 s= u'\u250c'+u'\u2500'*w0+u'\u2510' +'\n' for AA in A: s += ' ' + ndtotext(AA, w=w1) +'\n' s += u'\u2514'+u'\u2500'*w0+u'\u2518' elif A.ndim==3: h=A.shape[1] s1=u'\u250c' +'\n' + (u'\u2502'+'\n')*h + u'\u2514'+'\n' s2=u'\u2510' +'\n' + (u'\u2502'+'\n')*h + u'\u2518'+'\n' strings=[ndtotext(a)+'\n' for a in A] strings.append(s2) strings.insert(0,s1) s='\n'.join(''.join(pair) for pair in zip(*map(str.splitlines, strings))) return s 

y como ejemplo:

 shape = 4, 3, 6 B2=np.arange(np.prod(shape)).reshape(shape) print(B2) print(ndtotext(B2)) [[[ 0 1 2 3 4 5] [ 6 7 8 9 10 11] [12 13 14 15 16 17]] [[18 19 20 21 22 23] [24 25 26 27 28 29] [30 31 32 33 34 35]] [[36 37 38 39 40 41] [42 43 44 45 46 47] [48 49 50 51 52 53]] [[54 55 56 57 58 59] [60 61 62 63 64 65] [66 67 68 69 70 71]]] ┌┌───────────────────┐┌───────────────────┐┌───────────────────┐┌───────────────────┐┐ │ [0 1 2 3 4 5 ] [18 19 20 21 22 23] [36 37 38 39 40 41] [54 55 56 57 58 59] │ │ [6 7 8 9 10 11] [24 25 26 27 28 29] [42 43 44 45 46 47] [60 61 62 63 64 65] │ │ [12 13 14 15 16 17] [30 31 32 33 34 35] [48 49 50 51 52 53] [66 67 68 69 70 71] │ └└───────────────────┘└───────────────────┘└───────────────────┘└───────────────────┘┘ 

En cada uno de estos casos, cada instancia de su dimensión final se imprime en una sola línea. No hay nada inconsistente aquí.

Pruebe varias formas de:

 a = np.random.rand(5, 4, 3) print(a) 

Cambie el número de dimensiones en a (por ejemplo, agregando más enteros separados por comas). Encontrará que cada vez que imprima a , cada fila en el objeto impreso tendrá k valores, donde k es el último entero en la forma de a.