Как инициализировать структурированный массив NumPy с разными значениями по умолчанию для каждого столбца?

Я пытаюсь инициализировать структурированную матрицу NumPy размера (x, y), где значение x равно ~ 10^3, а значение y равно ~ 10^6.

Первый столбец матрицы — это идентификатор (целое число), а остальные — триплеты (int8), где каждый член триплета должен иметь другое значение по умолчанию.

т. е. предполагая, что значения по умолчанию равны [2,5,9]. Я хотел бы инициализировать следующую матрицу:

0 2 5 9 2 5 9 2 5 9 ...
0 2 5 9 2 5 9 2 5 9 ...
0 2 5 9 2 5 9 2 5 9 ...
0 2 5 9 2 5 9 2 5 9 ...
...

Проблема здесь (VS. this аналогичный вопрос) заключается в том, что каждый столбец имеет свое уникальное имя, которое необходимо записать.

Самый быстрый способ инициализации матрицы:

default_age       = 2
default_height    = 5
default_shoe_size = 9

columns = ["id", 
           "a_age", 
           "a_height", 
           "a_shoe_size", 
           "b_age", 
           "b_height", 
           "b_shoe_size",
           #...
           ]

y = len(columns)    
x = 10**4

# generate matrix
mat = numpy.zeros(shape=x,
                  dtype={"names"   : columns,
                         "formats" : ['i'] + ['int8'] * (len(columns) - 1)})
# fill the triplets with default values
for i in xrange(y/3):
    j = i * 3
    mat[mat.dtype.names[j+1]] = default_age
    mat[mat.dtype.names[j+2]] = default_height
    mat[mat.dtype.names[j+3]] = default_shoe_size

Каков самый быстрый способ инициализировать такую ​​матрицу?

Спасибо!


person NStiner    schedule 02.05.2015    source источник
comment
Есть ли причина, по которой вы не хотите просто использовать кадры данных pandas?   -  person jme    schedule 02.05.2015
comment
Что-то здесь не так. Вы создаете двумерный массив (с формой (x, len(columns))), и каждый элемент этого массива сам по себе является структурой с len(columns) полями. Вы уверены, что это то, что вы имели в виду? (Я предполагаю, что вам действительно нужен одномерный структурированный массив.)   -  person Warren Weckesser    schedule 02.05.2015
comment
Хотя я не переварил ваше описание структуры, мой опыт показывает, что копирование данных в структурированный массив, поле за полем, обычно является самым быстрым способом. Либо так, либо составьте список всех необходимых кортежей.   -  person hpaulj    schedule 02.05.2015
comment
@Warren Weckesser Вы правы - я действительно хотел создать одномерный структурированный массив, отредактировал вопрос, чтобы отразить это. Спасибо!   -  person NStiner    schedule 02.05.2015
comment
@hpaulj Матрица должна быть структурирована. Он также огромен и будет состоять в основном из значений по умолчанию, поэтому я хотел бы заполнить его ими как можно быстрее.   -  person NStiner    schedule 02.05.2015
comment
Прежде чем беспокоиться о «самом быстром», вы должны привести нам рабочий пример. Вы не указываете x или y, и ваша индексация mat[:,i+1] не будет работать со структурированным массивом.   -  person hpaulj    schedule 03.05.2015
comment
@hpaulj Спасибо, я исправил код, теперь он работает.   -  person NStiner    schedule 04.05.2015


Ответы (4)


Это моя настройка вашего образца, скорректированная так, чтобы он работал. Обратите внимание, что я перебираю столбцы по имени поля

dt=np.dtype({"names": columns, "formats" : ['i'] + ['int8'] * (len(columns) - 1)})
mat=np.zeros((10,),dtype=dt)
for i in range(1,7,3):
    mat[dt.names[i]]=default_age
    mat[dt.names[i+1]]=default_height
    mat[dt.names[i+2]]=default_shoe_size

производство

array([(0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9)], 
      dtype=[('id', '<i4'), ('collections.ChainMap(np.arange(6).reshape(3,2))[0]_age', 'i1'), ('a_height', 'i1'), ('a_shoe_size', 'i1'), ('b_age', 'i1'), ('b_height', 'i1'), ('b_shoe_size', 'i1')])

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

В моем образце x=(10,). Ваше выражение mat[:,j+1] не было исправлено для обработки структурированного массива 1d.

Структурированный массив, вероятно, не лучший способ, если у вас очень много столбцов (полей) (по сравнению с количеством строк).

Если все ваши поля «int», я бы использовал обычный массив 2d. Структурированные массивы наиболее полезны, когда поля имеют разные типы элементов.


Вот способ инициализации обычного массива 2d с этими значениями и, при необходимости, приведения его к структурированному массиву.

values=np.array([2,5,9])
x, y = 10, 2
mat1=np.repeat(np.repeat(values[None,:],y,0).reshape(1,3*y),x,0)

производство:

array([[2, 5, 9, 2, 5, 9],
       [2, 5, 9, 2, 5, 9],
       ...,
       [2, 5, 9, 2, 5, 9]])

Добавить в столбец идентификатора

mat1=np.concatenate([np.zeros((x,1),int),mat1],1)
array([[0, 2, 5, 9, 2, 5, 9],
       [0, 2, 5, 9, 2, 5, 9],
       ...
       [0, 2, 5, 9, 2, 5, 9],
       [0, 2, 5, 9, 2, 5, 9]])

Новый dtype — со всеми простыми «int»:

dt1=np.dtype({"names"   : columns, "formats" : ['i'] + ['int'] * (len(columns) - 1)})
mat2=np.empty((x,),dtype=dt1)

Если все сделано правильно, data для mat1 должен иметь тот же размер и порядок байтов, что и для mat2. В этом случае я могу «скопировать» его (на самом деле просто изменить указатели).

mat2.data=mat1.data

mat2 выглядит точно так же, как предыдущий mat, за исключением того, что dtype немного отличается (с полями i4 вместо i1)

array([(0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9)], 
      dtype=[('id', '<i4'), ('a_age', '<i4'), ('a_height', '<i4'), ('a_shoe_size', '<i4'), ('b_age', '<i4'), ('b_height', '<i4'), ('b_shoe_size', '<i4')])

Другой способ использовать значения mat1 для инициализации структурированного массива — это промежуточный список кортежей:

np.array([tuple(row) for row in mat1],dtype=dt)
array([(0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9)], 
      dtype=[('id', '<i4'), ('a_age', 'i1'), ('a_height', 'i1'), ('a_shoe_size', 'i1'), ('b_age', 'i1'), ('b_height', 'i1'), ('b_shoe_size', 'i1')])

Я не запускал временные тесты, отчасти потому, что не знаю, на что похожи ваши значения x, y.

Преобразование структурированного массива с различными числовыми типы данных в обычный массив

или из ответа в https://stackoverflow.com/a/21818731/901925 конструктор np.ndarray можно использовать для создать новый массив, используя уже существующий буфер данных. Все еще нужно использовать dt1, все i8 dtype.

np.ndarray((x,), dt1, mat1)

Также ndarray tostructured_array и float to int и многое другое. используя view v. astype для этого преобразования.

person hpaulj    schedule 02.05.2015

Вы можете создать массив, используя обычные tile и column_stack, предоставленные numpy, а затем использовать np.core.records.fromarrays:

import numpy as np

default_age       = 2
default_height    = 5
default_shoe_size = 9
n_rows = 10

columns = [
    "id", 
    "a_age", 
    "a_height", 
    "a_shoe_size", 
    "b_age", 
    "b_height", 
    "b_shoe_size",
    ]

# generate matrix
dtype = {
    "names": columns,
    "formats": ['i'] + ['int8'] * (len(columns) - 1)
    }

ids = np.zeros(n_rows)
people = np.tile([default_age, default_height, default_shoe_size], (n_rows,2))
data = np.column_stack((ids, people))

mat = np.core.records.fromarrays(list(data.T), dtype=dtype)

Который дает:

>>> mat
rec.array([(0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9), (0, 2, 5, 9, 2, 5, 9),
       (0, 2, 5, 9, 2, 5, 9)], 
      dtype=[('id', '<i4'), ('a_age', 'i1'), ('a_height', 'i1'), ('a_shoe_size', 'i1'), ('b_age', 'i1'), ('b_height', 'i1'), ('b_shoe_size', 'i1')])
person jme    schedule 02.05.2015

Вы можете заполнить значения по умолчанию с помощью цикла for. Если у вас есть значения по умолчанию, например, в словаре:

default_values = {
    "a_age": 3,
    "a_height": 5,
}
for column, value in default_values.items():
    mat[column] = value
person Daniel    schedule 02.05.2015
comment
Чем это отличается (с точки зрения производительности) от решения, которое я предложил? - person NStiner; 02.05.2015
comment
Где ваше решение? - person Daniel; 02.05.2015
comment
Это в самом вопросе. - person NStiner; 02.05.2015

Вы можете использовать перечисление для представления имен столбцов

class Columns(Enum):
    id = 0
    a_age = 1
    a_height = 2
    a_shoe_size = 3
    b_age = 4
    b_height = 5
    b_shoe_size = 6
    ...

Затем используйте обычный массив инициализации массивов и синтаксис доступа или любой другой объект, который вы хотите использовать. Например, вместо индекса столбца вы должны использовать Columns.a_age. Для получения дополнительной информации о перечислениях см. здесь Как я могу представить "Enum" в Питоне?

person Mitchell Carroll    schedule 03.05.2015