Numpy: замена нулей в массиве numpy массивом numpy - PullRequest
0 голосов
/ 23 ноября 2018

Работа с данными, которые я хочу развернуть.Обратите внимание, что я ограничен только numpy и не могу использовать панд.Исходные данные выглядят так:

data = [
  [ 1, a, [<metric1>, <metric2>] ],
  [ 1, b, [<metric1>, <metric2>] ],
  [ 2, b, [<metric1>, <metric2>] ],
  [ 2, c, [<metric1>, <metric2>] ],
  [ 3, a, [<metric1>, <metric2>] ],
  [ 3, c, [<metric1>, <metric2>] ],
  ...etc
]

Поворот моих данных с помощью numpy:

rows, row_pos = np.unique(data[:, row_index], return_inverse=True)
cols, col_pos = np.unique(data[:, col_index], return_inverse=True)
pivot_table = np.zeros((len(rows), len(cols)), dtype=object)
pivot_table[row_pos, col_pos] = data[:, pivot_index]

Полученный формат:

cols = [a, b, c, ...]
rows = [1, 2, 3, ...]
pivot_table = [
  [ [<metric1>, <metric2>], [<metric1>, <metric2>], 0, ... ],
  [ 0, [<metric1>, <metric2>], [<metric1>, <metric2>], ... ],
  [ [<metric1>, <metric2>], 0, [<metric1>, <metric2>], ... ],
  ...
]

Сводная таблица в конечном итогеотображается, где он отмечает, где находятся нули, и создает правильное количество ячеек, чтобы таблица была правильно отформатирована.

Это всего лишь временный обходной путь, поскольку изначально я пытался просто заменить нули на нольмассив (т. е. [0,0])

pivot_table[pivot_table == 0] = [0,0]

Но я получил следующую ошибку:

TypeError: NumPy boolean array indexing assignment requires a 0 or 1-dimensional input, input has 2 dimensions

Моего временного исправления было достаточно, но оно ограничено, когда я хочу что-то сделатьнапример, наличие ряда сумм столбцов.У меня есть несколько подходов, но я не знаю, как их выполнить:

  1. Как упоминалось выше, замена нулей после факта списком нулей
  2. При первоначальном создании таблицыиспользуя индексы np.unique, вместо нулей укажите значение по умолчанию для заполнения таблицы.
  3. Извлеките метрики из списка в массив, т. е. [ 1, a, <metric1>, <metric2> ].Это, вероятно, лучшее решение для упрощения агрегатных функций.

Какие-либо решения для любого из упомянутых подходов?

Ответы [ 3 ]

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

Ваши типы данных ввода не ясны, и могут создавать неудобства.Избегание типа объекта облегчает анализ структуры данных.Использование структурированного массива может помочь:

Пример необработанных данных:

n=10
data= [ [randint(5),'abcdef'[randint(6)],rand(2)] for _ in range(n)]

Ввод и заполнение вручную:

dt=np.dtype([('i', 'i4'), ('j', 'U1'), ('val', 'f8', 2)])
arr = ndarray(len(data),dtype=dt)
for k,(a,b,c) in enumerate (data):
    arr[k]['i']=a
    arr[k]['j']=b
    arr[k]['val']=c

Теперь все просто:

row=arr['i']
col=arr['j']
val=arr['val']

(r,ri),(c,ci) = (np.unique(x,return_inverse=True) for x in (row,col))
res=zeros((len(r),len(c),2)) # the good shape
res[ri,ci]=val

res сейчас

[[[ 0.87  0.96]
  [ 0.03  0.92]
  [ 0.45  0.55]
  [ 0.    0.  ]
  [ 0.    0.  ]]

 [[ 0.27  0.84]
  [ 0.    0.  ]
  [ 0.41  0.05]
  [ 0.47  0.67]
  [ 0.    0.  ]]

 [[ 0.3   0.05]
  [ 0.    0.  ]
  [ 0.    0.  ]
  [ 0.    0.  ]
  [ 0.37  0.76]]

 [[ 0.    0.  ]
  [ 0.    0.  ]
  [ 0.    0.  ]
  [ 0.    0.  ]
  [ 0.4   0.07]]]
0 голосов
/ 23 ноября 2018

Вот как заставить ваш подход 2 работать:

fillvalue = np.empty((), 'O')
fillvalue[()] = [0, 0]
pivot_table = np.full((len(rows), len(cols)), fillvalue)

etc.

Обратите внимание, что [0, 0] все являются одним и тем же объектом, поэтому если вы хотите изменить один из них, вам не следует делатьизменив объект списка на месте, вместо этого создайте новый список и назначьте его позиции массива.

Если вам нужен трехмерный числовой массив вместо массива списков, быстрое исправление - np.array(pivot_table.tolist()).

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

Попытка воссоздать ваш случай:

In [182]: a,b,c = 0,1,2
In [183]: metric1, metric2 = 100,200
In [186]: data = [
     ...:   [ 1, a, [metric1, metric2] ],
     ...:   [ 1, b, [metric1, metric2] ],
     ...:   [ 2, b, [metric1, metric2] ],
     ...:   [ 2, c, [metric1, metric2] ],
     ...:   [ 3, a, [metric1, metric2] ],
     ...:   [ 3, c, [metric1, metric2] ],
     ...: ]
In [187]: 
In [187]: data
Out[187]: 
[[1, 0, [100, 200]],
 [1, 1, [100, 200]],
 [2, 1, [100, 200]],
 [2, 2, [100, 200]],
 [3, 0, [100, 200]],
 [3, 2, [100, 200]]]

In [189]: data = np.array(data,object)
In [190]: rows, row_pos = np.unique(data[:, 0], return_inverse=True)
     ...: cols, col_pos = np.unique(data[:, 1], return_inverse=True)
     ...: pivot_table = np.zeros((len(rows), len(cols)), dtype=object)

In [191]: pivot_table
Out[191]: 
array([[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0]], dtype=object)
In [192]: pivot_table[row_pos, col_pos] = data[:, 2]
In [193]: pivot_table
Out[193]: 
array([[list([100, 200]), list([100, 200]), 0],
       [0, list([100, 200]), list([100, 200])],
       [list([100, 200]), 0, list([100, 200])]], dtype=object)
In [194]: pivot_table[row_pos, col_pos]
Out[194]: 
array([list([100, 200]), list([100, 200]), list([100, 200]),
       list([100, 200]), list([100, 200]), list([100, 200])], dtype=object)
In [195]: _.shape
Out[195]: (6,)
In [196]: data[:,2].shape
Out[196]: (6,)

Это назначение работает между исходной формой (и dtype), совпадающей с целевой (6,).

In [197]: mask = pivot_table==0
In [198]: mask
Out[198]: 
array([[False, False,  True],
       [ True, False, False],
       [False,  True, False]])
In [199]: pivot_table[mask]
Out[199]: array([0, 0, 0], dtype=object)
In [200]: pivot_table[mask] = [0,0]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-200-83e0a7422802> in <module>()
----> 1 pivot_table[mask] = [0,0]

ValueError: NumPy boolean array indexing assignment cannot assign 2 input values to the 3 output values where the mask is true

Другое сообщение об ошибке (другая версия NumPy?), но это говорит, что я пытаюсь положить 2 значения в 3 слота.Он не обрабатывает [0,0] как отдельный элемент, а как 2.

Нет проблем с назначением скалярного элемента:

In [203]: pivot_table[mask] = None
In [204]: pivot_table
Out[204]: 
array([[list([100, 200]), list([100, 200]), None],
       [None, list([100, 200]), list([100, 200])],
       [list([100, 200]), None, list([100, 200])]], dtype=object)

В прошлом я имелУспешное использование frompyfunc для создания массивов dtype объектов.Определите небольшую функцию.Я мог бы проверить на 0 или тип, но так как я уже вставил None, давайте проверим это:

In [205]: def fun(x):
     ...:     if x is None: return [0,0]
     ...:     return x

Примените его к каждому элементу pivot_table, создав новый массив.

In [230]: arr1 = np.frompyfunc(fun,1,1)(pivot_table)
In [231]: arr1
Out[231]: 
array([[list([100, 200]), list([100, 200]), list([0, 0])],
       [list([0, 0]), list([100, 200]), list([100, 200])],
       [list([100, 200]), list([0, 0]), list([100, 200])]], dtype=object)

Другой подход, давайте попробуем назначить список списков:

In [240]: pivot_table[mask] = [[0,0] for _ in range(3)]    
TypeError: NumPy boolean array indexing assignment requires a 0 or 1-dimensional input, input has 2 dimensions

Но если я попробую то же самое с where, это сработает:

In [241]: pivot_table[np.where(mask)] = [[0,0] for _ in range(3)]
In [242]: pivot_table
Out[242]: 
array([[list([100, 200]), list([100, 200]), list([0, 0])],
       [list([0, 0]), list([100, 200]), list([100, 200])],
       [list([100, 200]), list([0, 0]), list([100, 200])]], dtype=object)

С where это больше похоже на ваше первоначальное присвоение pivot_table.

In [243]: np.where(mask)
Out[243]: (array([0, 1, 2]), array([2, 0, 1]))

У индексации этого массива все еще могут быть проблемы с широковещательной передачей,

In [244]: pivot_table[np.where(mask)] = [0,0]
ValueError: cannot copy sequence with size 2 to array axis with dimension 3

Обычно логическая маскаИндекс ведет себя как эквивалентное np.where(mask) индексирование, но, очевидно, здесь взаимодействие объекта dtype и вещание путаются с логическим индексированием.


Out[231] по-прежнему (3,3) массив,хотя все элементы лен 2 списков.Чтобы превратить его в числовой массив, мы должны сделать что-то вроде:

In [248]: p = np.stack(pivot_table.ravel()).reshape(3,3,2)
In [249]: p
Out[249]: 
array([[[100, 200],
        [100, 200],
        [  0,   0]],

       [[  0,   0],
        [100, 200],
        [100, 200]],

       [[100, 200],
        [  0,   0],
        [100, 200]]])

np.concatenate*stack версии) могут объединять списки в массив, но он должен начинаться со списка или плоскогомассив, следовательно, необходимость в ravel и reshape.

np.array(pivot_table.tolist()) также работает.


Если бы вместо этого вы построили структурированный массив данных (предполагая, что значения metric являются числовыми):

In [265]: data1 = np.array([tuple(x.tolist()) for x in data],'i,i,2i')
In [266]: data1
Out[266]: 
array([(1, 0, [100, 200]), (1, 1, [100, 200]), (2, 1, [100, 200]),
       (2, 2, [100, 200]), (3, 0, [100, 200]), (3, 2, [100, 200])],
      dtype=[('f0', '<i4'), ('f1', '<i4'), ('f2', '<i4', (2,))])
In [267]: data1['f2']
Out[267]: 
array([[100, 200],
       [100, 200],
       [100, 200],
       [100, 200],
       [100, 200],
       [100, 200]], dtype=int32)

эти значения могут быть присвоены 3d pivot_table:

In [268]: p = np.zeros((len(rows), len(cols),2),int)
In [269]: p[row_pos, col_pos]=data1['f2']

С массивом fillvalue, который определил Пол Панцер, ваше первоначальное маскированное назначение работает:

In [322]: fillvalue = np.empty((), 'O')
     ...: fillvalue[()] = [0, 0]
     ...: 
In [323]: fillvalue
Out[323]: array(list([0, 0]), dtype=object)
In [324]: mask
Out[324]: 
array([[False, False,  True],
       [ True, False, False],
       [False,  True, False]])
In [325]: pivot_table[mask] = fillvalue

Его full делает np.copyto(a, fill_value, casting='unsafe'). Наше маскированное задание можно записать так: np.copyto(pivot_table, fillvalue, where=mask)

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