Преобразование массива 2D numpy в структурированный массив
Я пытаюсь преобразовать двумерный массив в структурированный массив с именованными полями. Я хочу, чтобы каждая строка в 2D-массиве была новой записью в структурированном массиве. К сожалению, ничто из того, что я пробовал, работает так, как я ожидаю.
Я начинаю с:
>>> myarray = numpy.array([("Hello",2.5,3),("World",3.6,2)])
>>> print myarray
[['Hello' '2.5' '3']
['World' '3.6' '2']]
Я хочу преобразовать в нечто похожее на это:
>>> newarray = numpy.array([("Hello",2.5,3),("World",3.6,2)], dtype=[("Col1","S8"),("Col2","f8"),("Col3","i8")])
>>> print newarray
[('Hello', 2.5, 3L) ('World', 3.6000000000000001, 2L)]
Что я пробовал:
>>> newarray = myarray.astype([("Col1","S8"),("Col2","f8"),("Col3","i8")])
>>> print newarray
[[('Hello', 0.0, 0L) ('2.5', 0.0, 0L) ('3', 0.0, 0L)]
[('World', 0.0, 0L) ('3.6', 0.0, 0L) ('2', 0.0, 0L)]]
>>> newarray = numpy.array(myarray, dtype=[("Col1","S8"),("Col2","f8"),("Col3","i8")])
>>> print newarray
[[('Hello', 0.0, 0L) ('2.5', 0.0, 0L) ('3', 0.0, 0L)]
[('World', 0.0, 0L) ('3.6', 0.0, 0L) ('2', 0.0, 0L)]]
Оба этих подхода пытаются преобразовать каждую запись в myarray в запись с данным dtype, поэтому добавляются дополнительные нули. Я не могу понять, как заставить его преобразовать каждую строку в запись.
Другая попытка:
>>> newarray = myarray.copy()
>>> newarray.dtype = [("Col1","S8"),("Col2","f8"),("Col3","i8")]
>>> print newarray
[[('Hello', 1.7219343871178711e-317, 51L)]
[('World', 1.7543139673493688e-317, 50L)]]
В этот раз фактическое преобразование не выполняется. Существующие данные в памяти просто повторно интерпретируются как новый тип данных.
Массив, с которого я начинаю, считывается из текстового файла. Типы данных не известны заранее, поэтому я не могу установить dtype во время создания. Мне нужно высокопроизводительное и элегантное решение, которое будет хорошо работать для общих случаев, так как я буду делать этот тип преобразования много раз во многих приложениях.
Спасибо!
Ответы
Ответ 1
Вы можете "создать массив записей из (плоского) списка массивов", используя numpy.core.records.fromarrays следующим образом:
>>> import numpy as np
>>> myarray = np.array([("Hello",2.5,3),("World",3.6,2)])
>>> print myarray
[['Hello' '2.5' '3']
['World' '3.6' '2']]
>>> newrecarray = np.core.records.fromarrays(myarray.transpose(),
names='col1, col2, col3',
formats = 'S8, f8, i8')
>>> print newrecarray
[('Hello', 2.5, 3) ('World', 3.5999999046325684, 2)]
Я пытался сделать что-то подобное. Я обнаружил, что когда numpy создал структурированный массив из существующего 2D-массива (используя np.core.records.fromarrays), он рассматривал каждый столбец (вместо каждой строки) в двухмерном массиве как запись. Поэтому вы должны перенести его. Такое поведение numpy не кажется очень интуитивным, но, возможно, для этого есть веская причина.
Ответ 2
Думаю,
new_array = np.core.records.fromrecords([("Hello",2.5,3),("World",3.6,2)],
names='Col1,Col2,Col3',
formats='S8,f8,i8')
- это то, что вы хотите.
Ответ 3
Хорошо, я боролся с этим какое-то время, но я нашел способ сделать это, что не требует больших усилий. Я прошу прощения, если этот код "грязный"....
Начнем с 2D-массива:
mydata = numpy.array([['text1', 1, 'longertext1', 0.1111],
['text2', 2, 'longertext2', 0.2222],
['text3', 3, 'longertext3', 0.3333],
['text4', 4, 'longertext4', 0.4444],
['text5', 5, 'longertext5', 0.5555]])
Итак, мы получаем 2D-массив с 4 столбцами и 5 строками:
mydata.shape
Out[30]: (5L, 4L)
Чтобы использовать numpy.core.records.arrays - нам нужно указать входной аргумент как список массивов, поэтому:
tuple(mydata)
Out[31]:
(array(['text1', '1', 'longertext1', '0.1111'],
dtype='|S11'),
array(['text2', '2', 'longertext2', '0.2222'],
dtype='|S11'),
array(['text3', '3', 'longertext3', '0.3333'],
dtype='|S11'),
array(['text4', '4', 'longertext4', '0.4444'],
dtype='|S11'),
array(['text5', '5', 'longertext5', '0.5555'],
dtype='|S11'))
Это создает отдельный массив для каждой строки данных. Но нам нужны входные массивы для столбца, поэтому нам понадобится:
tuple(mydata.transpose())
Out[32]:
(array(['text1', 'text2', 'text3', 'text4', 'text5'],
dtype='|S11'),
array(['1', '2', '3', '4', '5'],
dtype='|S11'),
array(['longertext1', 'longertext2', 'longertext3', 'longertext4',
'longertext5'],
dtype='|S11'),
array(['0.1111', '0.2222', '0.3333', '0.4444', '0.5555'],
dtype='|S11'))
Наконец, это должен быть список массивов, а не кортеж, поэтому мы завершаем приведенное выше в списке(), как показано ниже:
list(tuple(mydata.transpose()))
Это наш аргумент ввода данных отсортирован.... next - это dtype:
mydtype = numpy.dtype([('My short text Column', 'S5'),
('My integer Column', numpy.int16),
('My long text Column', 'S11'),
('My float Column', numpy.float32)])
mydtype
Out[37]: dtype([('My short text Column', '|S5'), ('My integer Column', '<i2'), ('My long text Column', '|S11'), ('My float Column', '<f4')])
Итак, теперь мы можем передать это на numpy.core.records.array():
myRecord = numpy.core.records.array(list(tuple(mydata.transpose())), dtype=mydtype)
... и пальцы скрещены:
myRecord
Out[36]:
rec.array([('text1', 1, 'longertext1', 0.11110000312328339),
('text2', 2, 'longertext2', 0.22220000624656677),
('text3', 3, 'longertext3', 0.33329999446868896),
('text4', 4, 'longertext4', 0.44440001249313354),
('text5', 5, 'longertext5', 0.5554999709129333)],
dtype=[('My short text Column', '|S5'), ('My integer Column', '<i2'), ('My long text Column', '|S11'), ('My float Column', '<f4')])
Voila! Вы можете индексировать по имени столбца, как в:
myRecord['My float Column']
Out[39]: array([ 0.1111 , 0.22220001, 0.33329999, 0.44440001, 0.55549997], dtype=float32)
Надеюсь, это поможет, так как я потратил столько времени на работу с numpy.asarray и mydata.astype и т.д., пытаясь заставить это работать, прежде чем, наконец, выработать этот метод.
Ответ 4
Если данные начинаются как список кортежей, то создание структурированного массива выполняется прямо:
In [228]: alist = [("Hello",2.5,3),("World",3.6,2)]
In [229]: dt = [("Col1","S8"),("Col2","f8"),("Col3","i8")]
In [230]: np.array(alist, dtype=dt)
Out[230]:
array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])
Усложнение состоит в том, что список кортежей был преобразован в массив строк 2d:
In [231]: arr = np.array(alist)
In [232]: arr
Out[232]:
array([['Hello', '2.5', '3'],
['World', '3.6', '2']],
dtype='<U5')
Мы могли бы использовать хорошо известный подход zip*
для "переноса" этого массива - на самом деле мы хотим иметь двойную транспонирование:
In [234]: list(zip(*arr.T))
Out[234]: [('Hello', '2.5', '3'), ('World', '3.6', '2')]
zip
удобно предоставил нам список кортежей. Теперь мы можем воссоздать массив с желаемым dtype:
In [235]: np.array(_, dtype=dt)
Out[235]:
array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])
В принятом ответе используется fromarrays
:
In [236]: np.rec.fromarrays(arr.T, dtype=dt)
Out[236]:
rec.array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])
Внутри fromarrays
используется общий подход recfunctions
: создайте целевой массив и скопируйте значения по имени поля. Эффективно это делает:
In [237]: newarr = np.empty(arr.shape[0], dtype=dt)
In [238]: for n, v in zip(newarr.dtype.names, arr.T):
...: newarr[n] = v
...:
In [239]: newarr
Out[239]:
array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])