web-dev-qa-db-fra.com

jolis ndarrays numpy d’impression utilisant des caractères unicode

J'ai récemment remarqué que la fonctionnalité d'impression Python n'était pas cohérente pour NumPy ndarays. Par exemple, il imprime un tableau 1D horizontal horizontalement:

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

mais un tableau horizontal 1D avec des crochets redondants verticalement:

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

un tableau vertical 1D horizontalement:

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

et un tableau 2D:

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

où la première dimension est maintenant verticale. Cela devient encore pire pour les dimensions plus élevées car toutes sont imprimées verticalement:

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

Un comportement cohérent à mon avis consisterait à imprimer les dimensions paires horizontalement et les dimensions impaires verticalement. En utilisant des caractères Unicode, il serait possible de le formater correctement. Je me demandais s'il était possible de créer une fonction pour imprimer les tableaux ci-dessus en tant que: 

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] │
       └└─────────┘└─────────┘┘ 

J'ai trouvé ceci Gist qui s'occupe du nombre différent de chiffres. J'ai essayé de prototyper une fonction récursive pour implémenter le concept ci-dessus:

 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]) 

Cela fonctionne un peu pour A1, A2, A3 et B mais pas pour C. J'apprécierais si vous pouviez m'aider à savoir comment la npprint devrait être d'obtenir la sortie ci-dessus pour une dimension arbitraire numpy ndarrays?

P.S.1. Dans l'environnement Jupyter, il est possible d'utiliser LaTeX \mathtools\underbracket et \overbracket dans Markdown. La jolie fonctionnalité d'impression de Sympy est également un excellent point de départ. Il peut utiliser ASCII, Unicode, LaTeX ...

P.S.2. On me dit qu'il y a effectivement une cohérence dans la façon dont les ndarrays sont imprimés. Cependant, à mon humble avis, il est un peu câblé et non intuitif. Avoir une jolie fonction d'impression flexible pourrait aider beaucoup à afficher ndarrays sous différentes formes. 

P.S.3. Les gars de Sympy ont déjà examiné les deux points que j'ai mentionnés ici. leur module Matrix est assez cohérent (A1 et A2 sont les mêmes) et ils ont aussi une fonction pprint qui fait un peu la même chose et que j'attends de npprint ici.

4
Foad

Cela a été une révélation pour moi de comprendre que les tableaux numpy ne ressemblent en rien aux matrices MATLAB ou aux tableaux mathématiques multidimensionnels que j'avais en tête. Ce sont des listes Python imbriquées plutôt homogènes et uniformes. J'ai également compris que la première dimension d'un tableau numpy est constituée des paires de crochets les plus profondes et les plus internes qui sont imprimées horizontalement, puis de la seconde dimension imprimée verticalement, la troisième verticalement avec une ligne espacée ...

Quoi qu'il en soit, je pense qu'une fonction ppring (inspirée par la convention de nommage de Sympy) pourrait être très utile. Je vais donc mettre une très mauvaise implémentation ici en espérant que cela inspirera d'autres Pythonistes avancés à proposer de meilleures solutions:

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')  

et le résultat est quelque peu acceptable pour les tableaux 1D et 2D:

B1=np.array([[111,122,133],[21,22,23],[31,32,33]])
pprint(B1)

#┌─────────────┐
# [111 122 133]
# [21  22  23 ]
# [31  32  33 ]
#└─────────────┘

c'est en effet un très mauvais code, il ne fonctionne que pour les entiers. J'espère que d'autres proposeront de meilleures solutions.

P.S.1.Eric Wieser a déjà mis en place un très beau prototype HTML pour IPython/Jupiter qui peut être vu ici :

 enter image description here

Vous pouvez suivre la discussion sur la liste de diffusion numpy ici

P.S.2. J'ai aussi posté cette idée ici sur Reddit .

P.S.3 J'ai passé un certain temps à étendre le code aux tableaux de dimensions 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

et à titre d'exemple:

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] │
└└───────────────────┘└───────────────────┘└───────────────────┘└───────────────────┘┘
7
Foad

Dans chacun de ces cas, chaque instance de votre dernière dimension est imprimée sur une seule ligne. Il n'y a rien d'incohérent ici.

Essayez différentes formes de:

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

Modifiez le nombre de dimensions dans a (par exemple, en ajoutant plusieurs nombres entiers séparés par des virgules). Vous constaterez qu'à chaque fois que vous imprimez a, chaque ligne de l'objet imprimé aura k valeurs, où k est le dernier entier de la forme de a.

0
duhaime