基于字段名称值对NumPy结构化数组进行排序的方法

4

我有以下结构化数组:

import numpy as np

x = np.rec.array([(22,2,200.,2000.), (44,2,400.,4000.), (55,5,500.,5000.), (33,3,400.,3000.)],
              dtype={'names':['subcase','id', 'vonmises','maxprincipal'], 'formats':['i4','i4','f4','f4']})

我正在尝试获取每个id的最大von Mises值。
例如,id为2的最大von Mises值为400。我也想要相应的子案例和最大主应力。
到目前为止,我已经完成了以下工作:
print repr(x[['subcase','id','vonmises']][(x['id']==2) & (x['vonmises']==max(x['vonmises'][x['id']==2]))])

这是输出结果:

array([(44, 2, 400.0)], 
  dtype=(numpy.record, [('subcase', '<i4'), ('id', '<i4'), ('vonmises', '<f4')]))

我现在遇到的问题是我希望这适用于数组中的所有 id,而不仅仅是 id=2。
即希望获得以下输出:
array([(44, 2, 400.0),(55, 5, 500.0),(33, 3, 400.0)], 
  dtype=(numpy.record, [('subcase', '<i4'), ('id', '<i4'), ('vonmises', '<f4')]))

有没有一种好的方法可以在不指定每个单独的ID的情况下完成此操作?
4个回答

3

我不知道为什么您使用这种格式,但是这里有一个使用 pandas 的技巧:

import pandas as pd

df  = pd.DataFrame(x)
df_ = df.groupby('id')['vonmises'].max().reset_index()

In [213]: df_.merge(df, on=['id','vonmises'])[['id','vonmises','subcase']]

Out[213]:
array([[   2.,  400.,   44.],
       [   3.,  400.,   33.],
       [   5.,  500.,   55.]], dtype=float32)

非常感谢,您能详细说明一下格式吗?您推荐使用哪种格式? - snowleopard
我不知道你试图解决的潜在问题是什么,但对于分组操作,通常建议采用 pandas 的 DataFrame 表示法来进行每个组的最大值和最大值筛选,例如 dfdf_,而不是使用这些 rec array。 - Colonel Beauvel
np.sort 允许您根据字段指定排序顺序,例如 ['id','vonmises']。但是,然后您必须使用类似于 intertools.groupby 的东西来提取每个组的第一个或最后一个。这是可能的,但比在 pandas 中更混乱。 - hpaulj
我可以知道这个替代方案被踩的原因吗? - Colonel Beauvel
我给你点了赞,但也许有人给你点了踩是因为你在我的问题中提到了pandas,而不是numpy。但我认为这是一个好的建议。了解市面上有哪些工具总是很有用的。 - snowleopard

3
这里有一种方法是使用np.sort(或argsort)接着使用itertools.groupby。但这个分组工具会产生一个生成器的生成器,操作起来比较混乱。
In [29]: x = np.rec.array([(22,2,200.,2000.), (44,2,400.,4000.), (55,5,500.,5000.), (33,3,400.,3000.)],
              dtype={'names':['subcase','id', 'vonmises','maxprincipal'], 'formats':['i4','i4','f4','f4']})

In [30]: ind=x.argsort(order=['id','vonmises'])

In [31]: ind
Out[31]: 
rec.array([0, 1, 3, 2], 
          dtype=int32)

In [32]: x[ind]
Out[32]: 
rec.array([(22, 2, 200.0, 2000.0), (44, 2, 400.0, 4000.0), (33, 3, 400.0, 3000.0),
 (55, 5, 500.0, 5000.0)], 
          dtype=[('subcase', '<i4'), ('id', '<i4'), ('vonmises', '<f4'), ('maxprincipal', '<f4')])

In [33]: import itertools

In [34]: [list(v) for k,v in itertools.groupby(x[ind],lambda i:i['id'])]
Out[34]: 
[[(22, 2, 200.0, 2000.0), (44, 2, 400.0, 4000.0)],
 [(33, 3, 400.0, 3000.0)],
 [(55, 5, 500.0, 5000.0)]]

接下来我们需要获取每个组的最后一条记录(对于最小值,获取第一条记录),然后重新构建recarray

In [39]: mx=[list(v)[-1] for k,v in itertools.groupby(x[ind],lambda i:i['id'])]

In [43]: np.rec.fromrecords(mx,dtype=x.dtype)
Out[43]: 
rec.array([(44, 2, 400.0, 4000.0), (33, 3, 400.0, 3000.0), (55, 5, 500.0, 5000.0)], 
          dtype=[('subcase', '<i4'), ('id', '<i4'), ('vonmises', '<f4'), ('maxprincipal', '<f4')])
mx的元素是具有正确dtypenp.record,但mx本身是一个列表。

或者简洁地说:

g=itertools.groupby(np.sort(x,order=['id','vonmises']), lambda i:i['id'])
np.rec.fromrecords([list(v)[-1] for k,v in g], dtype=x.dtype)

2

以下是一种不使用groupby的方法:

# sort as desired
x.sort(order=['id','vonmises'])

# keep the first element, and every element with a different id to the one before it
keep = np.empty(x.shape, dtype=np.bool)
keep[0] = True
keep[1:] = x[:-1].id != x[1:].id

x_filt = x[keep]

这将会给出:

rec.array([(22, 2, 200.0, 2000.0), (33, 3, 400.0, 3000.0), (55, 5, 500.0, 5000.0)], 
      dtype=[('subcase', '<i4'), ('id', '<i4'), ('vonmises', '<f4'), ('maxprincipal', '<f4')])

2
使用 numpy_indexed 包,这将变成一个简单的一行代码:
import numpy_indexed as npi
ids, maxvonmises = npi.group_by(x.id).max(x.vonmises)

这个库的性能可能类似于pandas,但更易读,并且不需要根据问题调整数据格式。


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