web-dev-qa-db-de.com

hübsches Drucken von numpy ndarrays mit Unicode-Zeichen

Ich habe kürzlich festgestellt, dass die Python-Druckfunktion für NumPy-Kalender nicht konsistent ist. Zum Beispiel wird ein horizontales 1D-Array horizontal gedruckt:

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

aber ein horizontales 1D-Array mit redundanten Klammern vertikal:

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

ein vertikales 1D-Array horizontal:

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

und ein 2D-Array:

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

wo die erste Dimension jetzt vertikal ist. Bei höheren Abmessungen wird es noch schlimmer, da alle vertikal gedruckt werden:

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

Ein konsequentes Verhalten wäre meiner Meinung nach, die geraden Maße horizontal und ungerade vertikal zu drucken. Mit Unicode-Zeichen wäre es möglich, es gut zu formatieren. Ich habe mich gefragt, ob es möglich ist, eine Funktion zu erstellen, um über Arrays zu drucken: 

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

Ich habe diese Gist gefunden, die sich um die unterschiedliche Anzahl von Ziffern kümmert. Ich habe versucht, eine rekursive Funktion zu entwickeln, um das obige Konzept zu implementieren:

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

Es funktioniert irgendwie für A1, A2, A3 und B, aber nicht für C. Ich würde mich freuen, wenn Sie mir helfen könnten, zu wissen, wie npprint für beliebig dimensionierte numpy ndarrays über der Ausgabe zu erreichen ist.

P.S.1. In der Jupyter-Umgebung kann LaTeX \mathtools\underbracket und \overbracket in Markdown verwendet werden. Sympys hübsche Druckfunktion ist auch ein guter Ausgangspunkt. Es kann ASCII, Unicode, LaTeX verwenden ...

P.S.2. Man sagt mir, dass die Art und Weise, in der die Arrays gedruckt werden, tatsächlich konsistent ist. IMHO ist es irgendwie verdrahtet und nicht intuitiv. Mit einer flexiblen, hübschen Druckfunktion kann es sehr hilfreich sein, ndarrays in verschiedenen Formen anzuzeigen. 

P.S.3. Sympy-Jungs haben bereits beide Punkte angesprochen, die ich hier erwähnt habe. Ihr Matrix-Modul ist ziemlich konsistent (A1 und A2 sind gleich) und sie haben auch eine pprint-Funktion, die irgendwie dasselbe bewirkt und ich erwarte von npprint hier.

4
Foad

Es war eine große Offenbarung für mich, wenn ich verstehe, dass numpy Arrays nichts mit MATLAB-Matrizen oder mehrdimensionalen mathematischen Arrays zu tun haben, die ich mir vorgestellt hatte. Sie sind ziemlich homogen und einheitlich verschachtelte Python-Listen. Ich habe auch verstanden, dass die erste Dimension eines numpy-Arrays die tiefsten/innersten Paare von eckigen Klammern ist, die horizontal gedruckt werden und dann von dort aus die zweite Dimension vertikal gedruckt wird.

Wie auch immer, ich denke, dass eine ppring-Funktion (inspiriert durch die Namenskonvention von Sympy) sehr helfen kann. Ich werde also eine sehr schlechte Implementierung hier setzen und hoffen, dass andere fortgeschrittene Pythoner dazu angeregt werden, bessere Lösungen zu finden:

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

und das Ergebnis ist für 1D- und 2D-Arrays einigermaßen akzeptabel:

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

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

dies ist in der Tat ein sehr schlechter Code, er funktioniert nur für ganze Zahlen. hoffentlich werden andere bessere lösungen finden.

P.S.1.Eric Wieser hat bereits einen sehr schönen HTML-Prototyp für IPython/Jupiter implementiert, der hier sehen kann:

 enter image description here

Sie können die Diskussion auf der numpy Mailingliste hier verfolgen. 

P.S.2. Ich habe diese Idee auch gepostet hier auf Reddit .

P.S.3 Ich habe einige Zeit gebraucht, um den Code auf dreidimensionale Arrays zu erweitern: 

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

und als Beispiel:

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

In jedem dieser Fälle wird jede Instanz Ihrer endgültigen Bemaßung in einer einzigen Zeile gedruckt. Hier gibt es nichts Unbeständiges.

Probieren Sie verschiedene Formen aus:

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

Ändern Sie die Anzahl der Dimensionen in a (z. B. durch Hinzufügen weiterer durch Kommas getrennte Ganzzahlen). Sie werden feststellen, dass jedes Mal, wenn Sie a drucken, jede Zeile im gedruckten Objekt k-Werte hat, wobei k die letzte Ganzzahl in as Form ist.

0
duhaime