在Python中按多列对numpy数组进行排序

9
我正在尝试按列1、列2和列3对以下数组进行排序。
[['2008' '1' '23' 'AAPL' 'Buy' '100']
 ['2008' '1' '30' 'AAPL' 'Sell' '100']
 ['2008' '1' '23' 'GOOG' 'Buy' '100']
 ['2008' '1' '30' 'GOOG' 'Sell' '100']
 ['2008' '9' '8' 'GOOG' 'Buy' '100']
 ['2008' '9' '15' 'GOOG' 'Sell' '100']
 ['2008' '5' '1' 'XOM' 'Buy' '100']
 ['2008' '5' '8' 'XOM' 'Sell' '100']]

我用了以下代码:
    idx=np.lexsort((order_array[:,2],order_array[:,1],order_array[:,0]))
    order_array=order_array[idx]

结果数组为:
[['2008' '1' '23' 'AAPL' 'Buy' '100']
 ['2008' '1' '23' 'GOOG' 'Buy' '100']
 ['2008' '1' '30' 'AAPL' 'Sell' '100']
 ['2008' '1' '30' 'GOOG' 'Sell' '100']
 ['2008' '5' '1' 'XOM' 'Buy' '100']
 ['2008' '5' '8' 'XOM' 'Sell' '100']
 ['2008' '9' '15' 'GOOG' 'Sell' '100']
 ['2008' '9' '8' 'GOOG' 'Buy' '100']]

问题在于最后两行是错误的。正确的数组应该将最后一行作为倒数第二行。我尝试了一切,但无法理解为什么会发生这种情况。非常感谢帮助。
我正在使用以下代码获取order_array。
 for i in ….
    x= ldt_timestamps[i] # this is a list of timestamps
    s_sym=……
    list=[int(x.year),int(x.month),int(x.day),s_sym,'Buy',100]   
    rows_list.append(list) 

 order_array=np.array(rows_list)

可能是按多个轴对2D numpy数组进行排序的重复问题。使用该答案,但使用适合您数据的dtype(不是所有字符串),例如dt = dt=[('y',np.uint32),('m',np.uint32),('d',np.uint32),('sym','S4'),('bs','S4'),('huh',np.uint32)] - askewchan
1个回答

11
简而言之:NumPy在对数值数组进行数值计算时表现出色。虽然这是可能的(见下文),但NumPy并不适合此类操作。您最好使用Pandas。
注:tldr代表“太长不看”,意思是总结或概述。
问题的原因:
这些值被排序时被视为字符串。你需要按照整数来排序。
In [7]: sorted(['15', '8'])
Out[7]: ['15', '8']

In [8]: sorted([15, 8])
Out[8]: [8, 15]

这是因为order_array包含字符串。您需要在适当的位置将这些字符串转换为ints
将数据类型从字符串数据类型转换为数字数据类型需要为新数组分配空间。因此,您最好从一开始就修改创建order_array的方式。
有趣的是,即使您将值转换为整数,当您调用时,它们仍然可能会被视为字符串。
order_array = np.array(rows_list)

NumPy默认创建一个同质数组。在同质数组中,每个值都具有相同的数据类型。因此,NumPy尝试找到所有值之间的共同点,并选择了字符串数据类型,从而挫败了您将字符串转换为整数的努力!
您可以通过检查order_array.dtype来自行检查数据类型:
In [42]: order_array = np.array(rows_list)

In [43]: order_array.dtype
Out[43]: dtype('|S4')

现在,我们该如何修复这个问题?
使用对象类型:
最简单的方法是使用“对象”dtype。
In [53]: order_array = np.array(rows_list, dtype='object')

In [54]: order_array
Out[54]: 
array([[2008, 1, 23, AAPL, Buy, 100],
       [2008, 1, 30, AAPL, Sell, 100],
       [2008, 1, 23, GOOG, Buy, 100],
       [2008, 1, 30, GOOG, Sell, 100],
       [2008, 9, 8, GOOG, Buy, 100],
       [2008, 9, 15, GOOG, Sell, 100],
       [2008, 5, 1, XOM, Buy, 100],
       [2008, 5, 8, XOM, Sell, 100]], dtype=object)

这里的问题是np.lexsortnp.sort不能用于object类型的数组。为了解决这个问题,您可以在创建order_list之前对rows_list进行排序:
In [59]: import operator

In [60]: rows_list.sort(key=operator.itemgetter(0,1,2))
Out[60]: 
[(2008, 1, 23, 'AAPL', 'Buy', 100),
 (2008, 1, 23, 'GOOG', 'Buy', 100),
 (2008, 1, 30, 'AAPL', 'Sell', 100),
 (2008, 1, 30, 'GOOG', 'Sell', 100),
 (2008, 5, 1, 'XOM', 'Buy', 100),
 (2008, 5, 8, 'XOM', 'Sell', 100),
 (2008, 9, 8, 'GOOG', 'Buy', 100),
 (2008, 9, 15, 'GOOG', 'Sell', 100)]

order_array = np.array(rows_list, dtype='object')

更好的选择是将前三列合并为datetime.date对象:
import operator
import datetime as DT

for i in ...:
    seq = [DT.date(int(x.year), int(x.month), int(x.day)) ,s_sym, 'Buy', 100]   
    rows_list.append(seq)
rows_list.sort(key=operator.itemgetter(0,1,2))        
order_array = np.array(rows_list, dtype='object')

In [72]: order_array
Out[72]: 
array([[2008-01-23, AAPL, Buy, 100],
       [2008-01-30, AAPL, Sell, 100],
       [2008-01-23, GOOG, Buy, 100],
       [2008-01-30, GOOG, Sell, 100],
       [2008-09-08, GOOG, Buy, 100],
       [2008-09-15, GOOG, Sell, 100],
       [2008-05-01, XOM, Buy, 100],
       [2008-05-08, XOM, Sell, 100]], dtype=object)

尽管这很简单,但我不喜欢dtype为object的NumPy数组。你既没有获得NumPy数组本机dtype所带来的速度优势,也没有节省内存空间。此时,你可能会发现使用Python列表更快且语法更易处理。

使用结构化数组:

一种更符合 NumPy 风格的解决方案,仍然具有速度和内存优势,是使用结构化数组(而不是同类数组)。要使用np.array创建结构化数组,您需要显式地提供 dtype:

dt = [('year', '<i4'), ('month', '<i4'), ('day', '<i4'), ('symbol', '|S8'),
      ('action', '|S4'), ('value', '<i4')]
order_array = np.array(rows_list, dtype=dt)

In [47]: order_array.dtype
Out[47]: dtype([('year', '<i4'), ('month', '<i4'), ('day', '<i4'), ('symbol', '|S8'), ('action', '|S4'), ('value', '<i4')])

要对结构化数组进行排序,您可以使用sort方法:

order_array.sort(order=['year', 'month', 'day'])

要使用结构化数组,您需要了解一些同质和结构化数组之间的区别:

您原始的同质数组是二维的。相比之下,所有的结构化数组都是一维的:

In [51]: order_array.shape
Out[51]: (8,)

如果您使用整数索引或遍历结构化数组,则会返回行:
In [52]: order_array[3]
Out[52]: (2008, 1, 30, 'GOOG', 'Sell', 100)

使用同构数组,您可以使用order_array[:, i]访问列。现在,使用结构化数组,您可以通过名称访问它们:例如order_array['year']

或者,使用Pandas:

如果您可以安装Pandas,我认为您最好使用Pandas DataFrame:

In [73]: df = pd.DataFrame(rows_list, columns=['date', 'symbol', 'action', 'value'])
In [75]: df.sort(['date'])
Out[75]: 
         date symbol action  value
0  2008-01-23   AAPL    Buy    100
2  2008-01-23   GOOG    Buy    100
1  2008-01-30   AAPL   Sell    100
3  2008-01-30   GOOG   Sell    100
6  2008-05-01    XOM    Buy    100
7  2008-05-08    XOM   Sell    100
4  2008-09-08   GOOG    Buy    100
5  2008-09-15   GOOG   Sell    100

Pandas有很多有用的函数,可以通过日期对时间序列进行对齐、填充缺失值、分组和聚合/转换行或列。
通常,拥有一个单一的日期列比三个整数值的年、月、日列更加有用。
如果你需要将年、月、日作为独立的列输出到csv等文件中,那么你可以用年、月、日列替换日期列,像这样:
In [33]: df = df.join(df['date'].apply(lambda x: pd.Series([x.year, x.month, x.day], index=['year', 'month', 'day'])))

In [34]: del df['date']

In [35]: df
Out[35]: 
  symbol action  value  year  month  day
0   AAPL    Buy    100  2008      1   23
1   GOOG    Buy    100  2008      1   23
2   AAPL   Sell    100  2008      1   30
3   GOOG   Sell    100  2008      1   30
4    XOM    Buy    100  2008      5    1
5    XOM   Sell    100  2008      5    8
6   GOOG    Buy    100  2008      9    8
7   GOOG   Sell    100  2008      9   15

或者,如果你一开始就没有使用“日期”列,当然可以保持 rows_list 不变,并从一开始就使用年、月、日列构建 DataFrame。排序仍然很容易:

df.sort(['year', 'month', 'day'])

@user2842122 - 这些'int'正在被转换回字符串。unutbu - 我认为这里最简单的解决方案可能是引入一个NumPy recarray,由一个NumPy datetime object和您剩余的字符串和整数数据组成。这里有一个完整的例子 - Aron Ahmadia
@AronAhmadia:感谢您的评论!是的,我正在考虑添加类似的内容,但我担心这个答案已经太长了,而且Pandas可能仍然是更好的选择。 - unutbu
当你手握一把锤子时,所有东西看起来都像钉子 :) 我同意作为SciPy堆栈的一部分,Pandas应该可用,并且对于这种工作具有更友好的界面。 - Aron Ahmadia
@unutbu:非常感谢您清晰的解释和各种解决方案。虽然“rows_list.sort”解决方案似乎对我来说最容易实现,但我采纳了您的建议并使用了pandas而不是numpy数组来解决。不过我有一个疑问-对于rows_list.sort和pandas解决方案,您都先将我的三列(yyyy、mm、dd)转换为一个日期时间列。为什么?这样做是为了只在一列上进行排序而不是三列吗?因为它确实会带来问题,因为我的最终数组必须有三列(yyyy、mm、dd)而不是一列。 - user2842122
虽然将前三列转换为日期并不是必须的,但通常这样做是一个好习惯。首先,它可以验证你所拥有的是否是一个日期(例如(2008,99,1)就不是一个有效的日期)。其次,一些操作,如排序,使用日期列比使用3个整数值列更容易表达,因为你只需要写df.sort(['date']而不是df.sort(['year', 'month', 'day']) - unutbu
显示剩余3条评论

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