Python 和 SPL 对比 5——有序运算

人们天然对序运算感兴趣,比上期、同期比等都是序运算,本文对比 Python 和 SPL 的有序运算。

排序

排序是最常见的运算了,如:

按日期对代码为 000062 的股票排序。

股票数据如下:

..

Python

import pandas as   pd

stock_file="D:/data/STOCKS.csv"

stock_data=pd.read_csv(stock_file,dtype={'STOCKID':'object'})

stock_62=stock_data.query('STOCKID=="000062"').copy()

stock_62["DATE"]=pd.to_datetime(stock_62["DATE"])

stock_62.sort_values(by="DATE",inplace=True)

print(stock_62)

导入 pandas

股票数据路径

读股票数据

选出股票

转换日期格式

按日期排序

 

Pandas提供了 sort_values 函数来对某列排序,操作起来还是很方便的。

SPL


A

B

1

D:\data\STOCKS.csv

/股票数据路径

2

=file(A1).import@tc(#1:string,#2,#3)

/读股票数据

3

=A2.select(STOCKID=="000062")

/选出股票

4

=A3.run(DATE=date(DATE,"yyyy/MM/dd"))

/转换日期格式

5

=A3.sort(DATE)

/排序

SPL用 sort(…) 函数来排序。

我们偶尔也需要对多列进行不同方向的排序,如:

将所有股票数据按股票代码升序,股价降序排序。

Python

#续用 stock_data

stock_sort2key=stock_data.sort_values(by=["STOCKID","CLOSING"],ascending=[1,0])

print(stock_sort2key)

 

多列不同方向排序

 

sort_values()中的 by 参数支持多列,ascending 参数支持不同方向排序,还是比较方便的。

SPL


A

B

/A2是股票信息


7

=A2.sort(STOCKID,-CLOSING)

/读股票数据

SPL仍用 sort(…) 函数完成多键,不同方向排序,同样方便。

排序后的位置 / 索引

排序后的位置信息也很重要,有时也会用到这个信息,如:

计算 000062 股票股价最高的三天的涨幅。

Python

#续用 stock_62

sort_pos=(-stock_62["CLOSING"]).argsort()

max3pos=sort_pos.iloc[:3]

stock_62s=stock_62.shift(1)

max3CL=stock_62["CLOSING"].iloc[max3pos]

max3CLs=stock_62s["CLOSING"].iloc[max3pos]

max3_rate=max3CL/max3CLs-1

print(max3_rate)

 

按 CLOSING 倒序排序后返回位置

取前 3 个位置

股票信息下移一行

CLOSING最高的 3 天价格

CLOSING最高的 3 天的前一天价格

CLOSING最高的 3 天股票涨幅

 

Python的 argsort(…) 可以返回排序后的位置信息。由于 Python 中没有循环函数,也不可以在循环过程中利用位置信息来计算,只能绕一下,先找到股价最高的 3 天的股价,再找到股价最高 3 天的前一天的股价,两者计算后得到涨幅,有点麻烦。

SPL


A

B

/A3是 000062 股票信息


9

=A3.psort@z(CLOSING)

/股价排序位置

10

=A9.m(:3)

/取前 3 个位置

11

=A3.calc(A10,if(#==1,null,CLOSING/CLOSING[-1]-1))

/按位置算涨幅

SPL中的 psort(…) 函数返回从小到大的位置信息,@z 选项则是逆序。calc(…) 函数是定位计算,利用成员的位置和相对位置进行计算。CLOSING[-1] 是当前成员的前一个成员。

这里计算股价最高 3 天的位置信息也可以用 ptop(…) 函数,这样省去了大排序,计算效率更高。如:A9 就不需要了,A10=A3.ptop(-3,CLOSING),A11 正常算。

延伸一下,当需要利用多列不同方向位置信息时,Python 和 SPL 又都是怎么处理的呢?

计算按股票代码升序、股价降序排序后的位置 / 索引。

Python

#续用 stock_data

import numpy as np

sort2key_pos=np.lexsort((-stock_data["CLOSING"],stock_data["STOCKID"]))

print(sort2key_pos)

 

导入 numpy

多列不同方向排序位置 / 索引

 

Python又是一个新函数 lexsort(…),而且还不是 Pandas 库里的,是 Numpy 库里的。正常的逻辑是先按 STOCKID 升序,再按 CLOSING 降序的,通常是从左往右重要度依次降低,但是 Python 中必须把先排序的 STOCKID 放在最后,然后是 CLOSING;如果是三个或者更多列排序,重要度要从右往左排,有点反人性了。而且这个函数没有逆序的参数,只能用负号“-”来表示逆序,但是 Python 又不支持字符串用负号,就会报错。这个问题如果是按 STOKID 降序,CLOSING 升序来排序,lexsort() 函数就用不了了,还得另想办法,感兴趣的同学可以动手试试看 Python 会有什么反应,代码是这样的:

np.lexsort((stock_data["CLOSING"],-stock_data["STOCKID"]))

SPL


A

B

/A2是股票信息


13

=A2.psort(STOCKID,-CLOSING)

/多列不同方向排序位置 / 索引

SPL就轻松太多了,和 sort 的函数完全一样,psort() 自然返回排序后的位置。而且 SPL 支持字符串取负号(-STOCKID 就是逆序排序)。

相邻记录引用

数据分析中,经常引用某种次序下的相邻记录来计算,如:

计算代码为 000062 的股票每天的涨跌幅。

Python

#续用上例中的 stock_62

stock_62s=stock_62["CLOSING"].shift(1)

stock_62["RATE"]=stock_62["CLOSING"]/stock_62s-1

print(stock_62)

 

前一天收盘价

计算涨跌幅

 

Python的 shift(n) 函数是移动 n 行数据,参数 n 可以是正数也可以是负数,如 1 是向下移动一行,-1 是向上移动一行,这样就可以利用移动后的数据来和当前行计算了。

SPL


A

B

/A3是 000062 股票信息


15

=A3.derive(if(#==1,null,CLOSING/CLOSING[-1]-1):RATE)

/增加涨跌幅

SPL中的循环函数,CLOSING[-1] 是在当前记录之前 1 个单位记录(即前一条记录)的 CLOSING 值,CLOSING[-1]=~[-1].CLOSING,这和上例中的 CLOSING[-1] 是一个道理。

SPL利用相对位置完成这样的运算,不像 Python 那样移动所有行,语法简洁性和运算效率都好。

 

移动平均也是常见的运算,如:

计算 000062 股票连续 5 个交易日的移动平均值。

Python

#续用的 stock_62

stock_62["MAVG"]=stock_62["CLOSING"].rolling(5,min_periods=1).mean()

print(stock_62)

 

计算移动平均

函数 rolling() 可以轻松计算移动平均,它算完以后是个对象,后续需要接聚合函数,如接 mean 就是移动平均,接 sum 就是移动求和,“对象”这个东西不太好理解。而且算前一行和相邻的 5 行平均值属于同一类运算,但 Python 确是两个不搭边的函数,shift 和 rolling。

SPL


A

B

/A3是 000062 股票信息


17

=A3.derive(CLOSING[-4:0].avg():DAY5)

/移动平均

SPL中还是用 CLOSING[…]形式来完成,只不过 [-4:0] 表示从之前第 4 行到当前行的全部记录。CLOSING[-4:0]=~[-4:0].(CLOSING)。这些相邻记录引用的形式是统一的,非常容易理解和记忆。这与序列的 ~[…]用法也一致,序列 ~[…]的用法在《Python 和 SPL 对比 3——循环函数》中的‘相邻引用’一节有过介绍。

小结

Python关于序运算的函数还是比较丰富的,但有点简单粗暴,如没有相对位置,只能将全体上移或下移,或者干脆用个对象来代替;位置计算的函数比较薄弱,有些需要自己硬编码或者“绕路”,编写程序和运行效率都会大打折扣。在函数的设计上也比较杂乱,不容易记忆。

SPL在序运算方面非常专业,拥有丰富的序运算函数,相对位置与绝对位置都可以在循环时轻松拿到,而且函数都是精心设计的,如 (sort,psort), (select,pselect), (max,pmax,maxp), (top,ptop)等等,函数意义类似,形式也类似,用起来就像乐高搭积木一样,知道几个原子函数(基本的积木形状)后很容易写出各种复杂代码(房子,飞机等复杂形状)。