красивая печать numy ndarrays с использованием символов Unicode - PullRequest
0 голосов
/ 03 ноября 2018

Я недавно заметил, что функции печати Python не совместимы с NumPy ndarays. Например, он печатает горизонтальный массив 1D по горизонтали:

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

, но 1D горизонтальный массив с избыточными вертикальными скобками:

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

1D вертикальный массив по горизонтали:

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

и двумерный массив:

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

где первое измерение теперь вертикальное. Для более высоких измерений становится еще хуже, поскольку все они печатаются вертикально:

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

По моему мнению, последовательное поведение состоит в том, чтобы печатать четные размеры по горизонтали и нечетные по вертикали. Используя символы Юникода, можно было бы красиво отформатировать его. Мне было интересно, если можно создать функцию для печати над массивами как:

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

Я нашел этот gist , который заботится о различном количестве цифр. Я попытался создать прототип рекурсивной функции для реализации вышеуказанной концепции:

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

Это вроде работает для A1, A2, A3 и B, но не для C. Буду признателен, если вы поможете мне узнать, как должно быть npprint для достижения указанного выше результата для numpy ndarrays произвольного измерения?

P.S.1. В среде Jupyter можно использовать LaTeX \mathtools \underbracket и \overbracket в Markdown. Симпатичная функциональность печати Sympy также является отличной отправной точкой. Может использовать ASCII, Unicode, LaTeX ...

P.S.2. Мне говорят, что действительно существует последовательность в способе печати ndarrays. однако ИМХО это отчасти проводной и не интуитивно понятный. Наличие гибкой симпатичной функции печати может очень помочь для отображения ndarrays в различных формах.

P.S.3. Симпатичные парни уже рассмотрели оба момента, которые я упомянул здесь. их матричный модуль довольно согласован (A1 и A2 одинаковы), и у них также есть функция pprint, которая делает то же самое, и я ожидаю от npprint здесь.

P.S.4. Для тех, кто придерживается этой идеи, я включил все это в в эту записную книжку Jupyter

Ответы [ 2 ]

0 голосов
/ 06 ноября 2018

Это было для меня настоящим открытием, что я понимаю, что массивы с пустяками не похожи на матрицы MATLAB или многомерные математические массивы. Это довольно однородные и однородные вложенные списки Python. Я также понял, что первое измерение массива numpy - это самая глубокая / внутренняя пара квадратных скобок, которая печатается горизонтально, а затем оттуда второе измерение печатается вертикально, третье - вертикально с разделительной линией ...

В любом случае, мне может помочь функция ppring (вдохновленная соглашением об именах Sympy). поэтому я приведу здесь очень плохую реализацию, надеясь, что она вдохновит других продвинутых Python-ов на поиск более эффективных решений:

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

и результат несколько приемлем для 1D и 2D массивов:

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

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

это действительно очень плохой код, он работает только для целых чисел. надеюсь, другие найдут лучшие решения.

P.S.1. Эрик Визер уже реализовал очень хороший HTML-прототип для IPython / Jupiter, который можно увидеть здесь :

enter image description here

Вы можете следить за обсуждением в списке рассылки NumPy здесь .

P.S.2. Я также разместил эту идею здесь, на Reddit .

P.S.3 Я потратил некоторое время, чтобы расширить код для трехмерных массивов:

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

и в качестве примера:

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] │
└└───────────────────┘└───────────────────┘└───────────────────┘└───────────────────┘┘
0 голосов
/ 03 ноября 2018

В каждом из этих случаев каждый экземпляр вашего окончательного размера печатается в одной строке. Здесь нет ничего противоречивого.

Попробуйте различные формы:

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

Измените количество измерений в a (например, добавив больше целых чисел, разделенных запятыми). Вы обнаружите, что каждый раз, когда вы печатаете a, каждая строка в печатном объекте будет иметь значения k, где k - последнее целое число в форме a.

...