Эквивалент названного кортежа в NumPy?
Можно ли создать объект NumPy, который ведет себя так же, как collection.namedtuple, в том смысле, что к элементам можно обращаться так:
data[1] = 42
data['start date'] = '2011-09-20' # Slight generalization of what is possible with a namedtuple
Я попытался использовать сложный тип данных:
>>> data = numpy.empty(shape=tuple(), dtype=[('start date', 'S11'), ('n', int)])
Это создает 0-мерное значение с типом namedtuple; он почти работает:
>>> data['start date'] = '2011-09-20'
>>> data
array(('2011-09-20', -3241474627884561860),
dtype=[('start date', '|S11'), ('n', '<i8')])
Однако доступ к элементу не работает, потому что "массив" является 0-мерным:
>>> data[0] = '2011-09-20'
Traceback (most recent call last):
File "<ipython-input-19-ed41131430b9>", line 1, in <module>
data[0] = '2011-09-20'
IndexError: 0-d arrays can't be indexed.
Есть ли способ получить желаемое поведение, описанное выше (назначение элемента как с строкой, так и с индексом) с объектом NumPy?
Ответы
Ответ 1
(отредактировано как EOL, рекомендованное для более конкретного ответа на вопрос.)
создать 0-мерный массив (я тоже не нашел скалярного конструктора.)
>>> data0 = np.array(('2011-09-20', 0), dtype=[('start date', 'S11'), ('n', int)])
>>> data0.ndim
0
элемент доступа в 0-мерном массиве
>>> type(data0[()])
<class 'numpy.void'>
>>> data0[()][0]
b'2011-09-20'
>>> data0[()]['start date']
b'2011-09-20'
>>> #There is also an item() method, which however returns the element as python type
>>> type(data0.item())
<class 'tuple'>
Я думаю, что проще всего подумать о структурированных массивах (или реперьятах) в виде списка или массивов кортежей, а индексация работает по имени, которое выбирает столбец и целые числа, которые выбирают строки.
>>> tupleli = [('2011-09-2%s' % i, i) for i in range(5)]
>>> tupleli
[('2011-09-20', 0), ('2011-09-21', 1), ('2011-09-22', 2), ('2011-09-23', 3), ('2011-09-24', 4)]
>>> dt = dtype=[('start date', '|S11'), ('n', np.int64)]
>>> dt
[('start date', '|S11'), ('n', <class 'numpy.int64'>)]
нулевой размерный массив, элемент является кортежем, т.е. одна запись, изменена: не является скалярным элементом, см. в конце
>>> data1 = np.array(tupleli[0], dtype=dt)
>>> data1.shape
()
>>> data1['start date']
array(b'2011-09-20',
dtype='|S11')
>>> data1['n']
array(0, dtype=int64)
массив с одним элементом
>>> data2 = np.array([tupleli[0]], dtype=dt)
>>> data2.shape
(1,)
>>> data2[0]
(b'2011-09-20', 0)
1d массив
>>> data3 = np.array(tupleli, dtype=dt)
>>> data3.shape
(5,)
>>> data3[2]
(b'2011-09-22', 2)
>>> data3['start date']
array([b'2011-09-20', b'2011-09-21', b'2011-09-22', b'2011-09-23',
b'2011-09-24'],
dtype='|S11')
>>> data3['n']
array([0, 1, 2, 3, 4], dtype=int64)
прямое индексирование в одну запись, так же, как в примере EOL, что я не знал, что он работает
>>> data3[2][1]
2
>>> data3[2][0]
b'2011-09-22'
>>> data3[2]['n']
2
>>> data3[2]['start date']
b'2011-09-22'
пытается понять пример EOL: скалярный элемент и нульмерный массив различаются
>>> type(data1)
<class 'numpy.ndarray'>
>>> type(data1[()]) #get element out of 0-dim array
<class 'numpy.void'>
>>> data1[0]
Traceback (most recent call last):
File "<pyshell#98>", line 1, in <module>
data1[0]
IndexError: 0-d arrays can't be indexed
>>> data1[()][0]
b'2011-09-20'
>>> data1.ndim
0
>>> data1[()].ndim
0
(Примечание. Я случайно набрал пример в открытом интерпретаторе python 3.2, так что есть b '...')
Ответ 2
Вы можете сделать что-то подобное с помощью модуля numpy.rec
. Вам нужен класс record
из этого модуля, но я не знаю, как напрямую создать экземпляр такого класса. Один верный способ - сначала создать recarray
с одной записью:
>>> a = numpy.recarray(1, names=["start date", "n"], formats=["S11", "i4"])[0]
>>> a[0] = "2011-09-20"
>>> a[1] = 42
>>> a
('2011-09-20', 42)
>>> a["start date"]
'2011-09-20'
>>> a.n
42
Если вы выясните, как создать экземпляр record
напрямую, сообщите мне.
Ответ 3
Это прекрасно реализовано в "Серии" в пакете Pandas.
Например, из учебника:
>>> from pandas import *
>>> import numpy as np
>>> s = Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
>>> s
a -0.125628696947
b 0.0942011098937
c -0.71375003803
d -0.590085433392
e 0.993157363933
>>> s[1]
0.094201109893723267
>>> s['b']
0.094201109893723267
Я только что играл с этим в течение нескольких дней, но похоже, что у него есть что предложить.
Ответ 4
ОК, я нашел решение, но мне бы хотелось увидеть более элегантный:
data = numpy.empty(shape=1, dtype=[('start date', 'S11'), ('n', int)])[0]
создает 1-мерный массив с одним элементом и получает элемент. Это заставляет элементы доступа работать как с строками, так и с числовыми индексами:
>>> data['start date'] = '2011-09-20' # Contains a space: more flexible than a namedtuple!
>>> data[1] = 123
>>> data
('2011-09-20', 123)
Было бы неплохо, если бы существовал способ прямого построения data
, без необходимости сначала создавать массив с одним элементом и извлекать этот элемент. Так как
>>> type(data)
<type 'numpy.void'>
Я не уверен, что может быть вызван конструктор NumPy... (для numpy.void
нет docstring).