如何为NumPy结构化数组的每一列初始化不同的默认值?

3
我正在尝试初始化一个大小为(x,y)的NumPy结构化矩阵,其中x的值为~10^3,y的值为~10^6。矩阵的第一列是ID(整数),其余列是三元组(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 ...
...

这里的问题(与this similar question相比)在于每个列都有一个不同的唯一名称应该被记录下来。最快的初始化矩阵的方法是:
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

什么是初始化这样一个矩阵的最快方式?
谢谢!

你有什么理由不想使用pandas数据框架吗? - jme
1
这里有些可疑。您正在创建一个2D数组(形状为(x,len(columns))),并且该数组的每个元素本身都是具有len(columns)字段的结构体。您确定这正是您想要的吗?(我猜您真正想要的是一维结构化数组。) - Warren Weckesser
虽然我还没有完全理解你的结构描述,但我的经验是逐个字段地将数据复制到结构化数组中通常是最快的方法。或者可以创建一个包含所有必要元组的列表。 - hpaulj
@Warren Weckesser 您是正确的 - 我的确打算创建一个1D结构化数组,已经编辑了问题以反映这一点。谢谢! - NStiner
1
在考虑“最快”的问题之前,您应该给我们一个可工作的示例。您没有指定xy,并且您的mat[:,i+1]索引在使用结构化数组时将无法工作。 - hpaulj
显示剩余2条评论
4个回答

3
这是我对你的示例进行调整后的修改版,已经可以运行。请注意,我通过字段名称迭代列。
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]表达式还没有被更正以处理结构化的一维数组。如果您有很多列(字段)(与行数相比),那么结构化数组可能不是最好的选择。如果所有字段都是'int',我会使用常规的二维数组。当字段具有不同类型的元素时,结构化数组最有用。
这是一种使用这些值初始化常规二维数组并可选择将其转换为结构化数组的方法。
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]])

添加id列
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]])

一个新的数据类型 - 全部为普通“int”:
dt1=np.dtype({"names"   : columns, "formats" : ['i'] + ['int'] * (len(columns) - 1)})
mat2=np.empty((x,),dtype=dt1)

如果做得正确,mat1data 应该与 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')])

我还没有进行时间测试,部分原因是因为我不知道你的 xy 值是什么样子的。

将具有各种数值数据类型的结构化数组转换为常规数组

或者从 https://dev59.com/x3zaa4cB1Zd3GeqPRpGR#21818731 的答案中可以看到,np.ndarray 构造函数可用于使用预先存在的数据缓冲区创建新数组。它仍然需要使用全部为 i8dt1

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

此外,将ndarray转换为structured_array和float转换为int,更多关于使用view v. astype 进行转换的内容。

1
你可以使用numpy提供的常规tilecolumn_stack来构建一个数组,然后使用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')])

-1

你可以使用for循环填充默认值。如果你的默认值例如在一个字典中:

default_values = {
    "a_age": 3,
    "a_height": 5,
}
for column, value in default_values.items():
    mat[column] = value

这个解决方案在性能上与我提出的方案有什么不同? - NStiner
你的解决方案在哪里? - Daniel
问题本身就已经包含了答案。 - NStiner

-1
你可以使用枚举来表示列名。
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即可。有关枚举的更多信息,请查看此处如何在Python中表示“Enum”?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接