本文将具体介绍Orca的装置办法、基本操作,以及Orca绝对pandas的差别,用户在应用Orca编程时须要留神的细节,以便用户能充分利用DolphinDB的劣势,写出高效的Orca代码。
- 装置
Orca反对Linux和Windows零碎,要求Python版本为3.6及以上,pandas版本为0.25.1及以上。Orca我的项目曾经集成到DolphinDB Python API中。通过pip工具装置DolphinDB Python API,就能够应用Orca。
pip install dolphindb
Orca是基于DolphinDB Python API开发的,因而,用户须要有一个DolphinDB服务器,并通过connect函数连贯到这个服务器,而后运行Orca:
>>> import dolphindb.orca as orca >>> orca.connect(MY_HOST, MY_PORT, MY_USERNAME, MY_PASSWORD)
如果用户曾经有现成的pandas程序,能够将pandas的import替换为:
# import pandas as pd import dolphindb.orca as pd pd.connect(MY_HOST, MY_PORT, MY_USERNAME, MY_PASSWORD)
- 疾速入门
通过传入一列值创立一个Orca Series对象。Orca会主动为它增加一个默认索引:
>>> s = orca.Series([1, 3, 5, np.nan, 6, 8]) >>> s 0 1.0 1 3.0 2 5.0 3 NaN 4 6.0 5 8.0 dtype: float64
通过传入一个字典创立与Orca DataFrame对象。字典中的每个元素必须是能转化为相似Series的对象:
>>> df = orca.DataFrame( ... {"a": [1, 2, 3, 4, 5, 6], ... "b": [100, 200, 300, 400, 500, 600], ... "c": ["one", "two", "three", "four", "five", "six"]}, ... index=[10, 20, 30, 40, 50, 60]) >>> df a b c 10 1 100 one 20 2 200 two 30 3 300 three 40 4 400 four 50 5 500 five 60 6 600 six
也能够间接传入一个pandas DataFrame以创立Orca DataFrame:
>>> dates = pd.date_range('20130101', periods=6) >>> pdf = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD')) >>> df = orca.DataFrame(pdf) >>> df A B C D 2013-01-01 0.758590 -0.180460 -0.066231 0.259408 2013-01-02 1.165941 0.961164 -0.716258 0.143499 2013-01-03 0.441121 -0.232495 -0.275688 0.516371 2013-01-04 0.281048 -0.782518 -0.683993 -1.474788 2013-01-05 -0.959676 0.860089 0.374714 -0.535574 2013-01-06 1.357800 0.729484 0.142948 -0.603437
当初df就是一个Orca DataFrame了:
>>> type(df) <class 'orca.core.frame.DataFrame'>
间接打印一个Orca对象时,服务端通常会把对应的整个DolphinDB数据传送到本地,这样做可能会造成不必要的网络开销。用户能够通过head函数查看一个Orca对象的顶部数行:
>>> df.head() A B C D 2013-01-01 0.758590 -0.180460 -0.066231 0.259408 2013-01-02 1.165941 0.961164 -0.716258 0.143499 2013-01-03 0.441121 -0.232495 -0.275688 0.516371 2013-01-04 0.281048 -0.782518 -0.683993 -1.474788 2013-01-05 -0.959676 0.860089 0.374714 -0.535574
通过index, columns查看数据的索引、列名:
>>> df.index DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'], dtype='datetime64[ns]', freq='D') >>> df.columns Index(['A', 'B', 'C', 'D'], dtype='object')
通过to_pandas把一个Orca DataFrame转换成pandas DataFrame:
>>> pdf1 = df.to_pandas() >>> type(pdf1) <class 'pandas.core.frame.DataFrame'>
通过read_csv加载一个CSV文件,要求CSV文件位于DolphinDB服务端,所给的门路是它在服务端的门路:
>>> df = orca.read_csv("/home/DolphinDB/Orca/databases/USPrices.csv")
- Orca的架构
Orca的顶层是pandas API,底层是DolphinDB数据库,通过DolphinDB Python API实现Orca客户端与DolphinDB服务端的通信。Orca的根本工作原理是,在客户端通过Python生成DolphinDB脚本,将脚本通过DolphinDB Python API发送到DolphinDB服务端解析执行。Orca的DataFrame中只存储对应的DolphinDB的表的元数据,真正的存储和计算都是在服务端。
Orca如何贮存数据
Orca对象在DolphinDB中以一个DolphinDB表的模式贮存。无论是Orca DataFrame还是Orca Series,它们的底层存储都是DolphinDB表,数据列和索引列存储在同一个表中。一个Orca DataFrame所示意的DolphinDB表蕴含若干数据列,以及若干索引列。而一个Orca Series所示意的DolphinDB表蕴含一列数据列,以及若干索引列。这使得索引对齐、表内各列计算、分组聚合等操作都能较容易地实现。
Orca的DataFrame中只存储对应的DolphinDB的表的元数据,包含表名、数据的列名、索引的列名等。如果尝试拜访一个DataFrame的列,返回Series时并不会创立一个新的表。返回的Series和原有的DataFrame应用同一个表,只是Orca对象所记录的元数据产生了变动。
- Orca的性能限度
因为Orca的架构,Orca的接口有局部限度:
- 列的数据类型
DolphinDB的表的每一个列必须指定一种数据类型。DolphinDB的ANY类型不能作为列的数据类型。因而,Orca的每一个列不能包含混合的数据类型。此外,列中的数据也不容许是一个DolphinDB不反对的Python对象,例如Python内置的list, dict,或规范库中的datetime等对象。
某些为这些DolphinDB不反对的类型而设计的函数,例如DataFrame.explode,在Orca中就没有实际意义。
- 列名的限度
DolphinDB的表中的列名必须是非法的DolphinDB变量名,即,仅蕴含字母、数字或下划线,且以字母结尾,且不是DolphinDB的保留字,比方if。
DolphinDB不容许反复的列名。因而Orca的列名不能反复。
以大写字母加下划线ORCA_结尾的列名是Orca的列名保留字,Orca会在外部将某些非凡的列(比方index)以这种模式命名。用户应该防止应用这类字符串作为Orca的列名,否则可能会呈现预期之外的行为。
- 分区表没有严格程序关系
如果DataFrame对应的DolphinDB表是一个分区表,数据存储并非间断,所以就没有RangeIndex的概念。DolphinDB分区表的各分区之间没有严格程序关系。因而,如果一个DataFrame示意的是一个DolphinDB分区表,这些操作无奈实现:
(1)对分区表通过iloc拜访相应的行
(2)将一个不同分区类型的Series或DataFrame赋值给一个DataFrame
- 局部函数仅不反对分布式调用
DolphinDB的某些内置函数目前暂不反对分布式的版本,例如median, quantile, mad。
- 空值机制不同
DolphinDB的数值空值是用每个数据类型的最小值示意。而pandas的空值是用浮点数的nan示意。Orca的空值机制和DolphinDB保持一致,仅当产生网络传输(下载)时,会将DolphinDB蕴含空值的数值列转化成浮点数类型,将其中的空值转化为nan。
对于字符串类型,pandas的空值仍然是nan,这就导致,pandas在贮存蕴含空值的字符串时,实际上是应用字符串和浮点数混合类型。而混合类型的列在DolphinDB中是不容许的。DolphinDB用空字符串示意字符串类型的空值。用户如果想要上传一个蕴含空值的字符串,应该对字符串列进行预处理,填充空值:
df = pd.DataFrame({"str_col": ["hello", "world", np.nan]}) odf = orca.DataFrame(df) # Error odf = orca.DataFrame(df.fillna({"str_col": ""})) # Correct way to upload a string column with NULL values
- 轴(axis)的限度
DolphinDB作为列式存储的数据库,对逐行(row-wise)操作的反对要好于逐列(column-wise)操作。许多操作,例如求和、求平均值等聚合运算,跨行的聚合(求每一列的函数值)的性能要高于跨列的聚合(求每一行的函数值),大多函数都反对跨行计算,但仅有大量函数,例如sum, mean, max, min, var, std等,反对跨列计算。在pandas中,在函数的参数中指定axis=0或axis=’index’就能实现跨行的计算,而指定axis=1或axis=’columns’能实现跨列的计算。而Orca函数经常仅反对axis=0或axis=’index’。
Orca的DataFrame也不反对transpose(转置)操作。因为转置后的DataFrame中的一列就可能蕴含混合类型的数据。
- 不承受Python可调用对象作为参数
DolphinDB Python API目前无奈解析Python函数,因而,例如DataFrame.apply, DataFrame.agg等函数无奈承受一个Python可调用对象作为参数。
对于这个限度,Orca提供了一个备选计划:传入一个DolphinDB字符串,它能够是DolphinDB的内置函数、自定义函数或条件表达式等。具体内容请参考高阶函数一节。
- 最佳实际
- 缩小to_pandas和from_pandas的调用
orca应用DolphinDB Python API与服务端通信。理论的数据贮存、查问和计算都产生在服务端,orca仅仅是一个提供了相似pandas接口的客户端。因而,零碎的瓶颈经常在网络通信上。用户在编写高性能的orca程序时,须要关注如何优化程序,以缩小网络通信量。
调用to_pandas函数将orca对象转化为pandas对象时,服务端会把整个DolphinDB对象传输到客户端。如果没有必要,个别应该缩小这样的转换。此外,以下操作会隐式调用to_pandas,因而也须要留神:
(1)打印一个示意非分区表的Orca DataFrame或Series
(2)调用to_numpy或拜访values
(3)调用Series.unique, orca.qcut等返回numpy.ndarray的函数
(4)调用plot相干函数画图
(5)将Orca对象导出为第三方格局的数据
相似地,from_pandas会将本地的pandas对象上传到DolphinDB服务端。当orca.DataFrame和orca.Series的data参数为非Orca对象时,也会先在本地创立一个pandas对象,而后上传到DolphinDB服务端。在编写Orca代码时,应该思考缩小来回的网络通信。
- Orca并非总是立即求值
Orca采纳了惰性求值策略,某些操作不会立即在服务端计算,而是转化成一个两头表达式,直到真正须要时才产生计算。须要触发计算时,用户应调用compute函数。例如,对同一个DataFrame中的列进行四则运算,不会立即触发计算:
>>> df = orca.DataFrame({"a": [1, 2, 3], "b": [10, 10, 30]}) >>> c = df["a"] + df["b"] >>> c # not calculated yet <orca.core.operator.ArithExpression object at 0x0000027FA5527B70> >>> c.compute() # trigger the calculation 0 11 1 12 2 33 dtype: int64
又如,条件过滤查问不会立即触发计算:
>>> d = df[df["a"] > 2] >>> d <orca.core.frame.DataFrame object with a WHERE clause> >>> d.compute() # trigger the calculation a b 2 3 30
分组后应用cumsum等函数聚合,或调用transform,也不会立即返回后果:
>>> c = df.groupby("b").cumsum() >>> c <orca.core.operator.DataFrameContextByExpression object at 0x0000017C010692B0> >>> c.compute() # trigger the calculation a 0 1 1 3 2 3 >>> c = df.groupby("b").transform("count") >>> c <orca.core.operator.DataFrameContextByExpression object at 0x0000012C414FE128> >>> c.compute() # trigger the calculation a 0 2 1 2 2 1
- 操作同一个DataFrame里的列以进步性能
如果操作的是同一个DataFrame里的列,Orca能够将这些操作优化为单个DolphinDB SQL表达式。这样的操作会有较高性能。例如:
(1)逐元素计算:df.x + df.y, df * df, df.x.abs()
(2)过滤行的操作:df[df.x > 0]
(3)isin操作:df[df.x.isin([1, 2, 3])]
(4)工夫类型/字符串拜访器:df.date.dt.month
(5)用同样长度的计算结果赋值:df[“ret”] = df[“ret”].abs()
当DataFrame是通过过滤的后果时,如果过滤的条件完全相同(在Python中是同一个对象,即调用id函数取得的值雷同),也能做到这样的优化。
以下脚本能够优化:
df[df.x > 0] = df[df.x > 0] + 1
上述脚本中,等号两边的过滤条件尽管看似雷同,但在Python中理论产生了两个不同的对象。在DolphinDB引擎中会先执行一个select语句,再执行一个update语句。如果将这个过滤条件赋值给一个两头变量,Orca就能够将上述代码优化为单个DolphinDB的update语句:
df_x_gt_0 = df.x > 0 df[df_x_gt_0] = df[df_x_gt_0] + 1
- 批改表数据的限度
在DolphinDB中,一个表的列的数据类型无奈批改。
此外,一个非内存表(例如DFS表)有这些限度:
(1)无奈增加新的列
(2)无奈通过update语句批改其中的数据
而一个分区表有这些限度:
(1)不同分区的数据之间没有严格的程序关系
(2)无奈通过update语句将一个向量赋值给一个列
因而,当用户尝试对一个Orca对象进行批改时,操作可能会失败。Orca对象的批改有以下规定:
(1)更新的数据类型不兼容,例如将一个字符串赋值给一个整数列时,会抛出异样
(2)为一个示意非内存表的orca对象增加列,或批改其中的数据时,会将这个表复制为内存表中,并给出一个正告
(3)主动为一个示意分区表的orca对象增加默认索引时,并不会真正增加一个列,此时会给出一个正告
(4)为一个示意分区表的orca对象设置或增加一个列时,如果这个列是一个Python或numpy数组,或一个示意内存表的orca Series时,会抛出异样
当尝试给示意非内存表的orca对象增加列,或批改其中数据时,数据会复制为内存表,而后再进行批改。当解决海量数据时,可能导致内存不足。因而应该尽量避免对这类orca对象的批改操作。
Orca局部函数不反对inplace参数。因为inplace波及到批改数据自身。
例如,以下orca脚本尝试为df增加一个列,会将DFS表复制为内存表,在数据量较大时可能会有性能问题:
df = orca.load_table("dfs://orca", "tb") df["total"] = df["price"] * df["amount"] # Will copy the DFS table as an in-memory segmented table! total_group_by_symbol = df.groupby(["date", "symbol"])["total"].sum()
以上脚本能够优化,不设置新的列,以防止大量数据复制。本例采纳的优化办法是将分组字段date和symbol通过set_index设置为索引,并通过指定groupby的level参数,按索引字段进行分组聚合,指定groupby的lazy参数为True,不立即对total进行计算。这样做,能防止增加一个新的列:
df = orca.load_table("dfs://orca", "tb") df.set_index(["date", "symbol"], inplace=True) total = df["price"] * df["amount"] # The DFS table is not copied total_group_by_symbol = total.groupby(level=[0,1], lazy=True).sum()
- 高阶函数
pandas的许多接口,例如DataFrame.apply, GroupBy.filter等,都容许承受一个Python的可调用对象作为参数。Orca实质上是通过Python API,将用户的程序解析为DolphinDB的脚本进行调用。因而,Orca目前不反对解析Python的可调用对象。如果用户传入一个或多个可调用对象,这些函数会尝试将Orca对象转换为pandas对象,调用pandas的对应接口,而后将后果转换回Orca对象。这样做不仅带来额定的网络通信,也会返回一个新的DataFrame,使得局部计算无奈达到在同一个DataFrame上操作时那样的高性能。
作为代替计划,对于这些接口,Orca能够承受一个字符串,将这个字符串传入DolphinDB进行计算。这个字符串能够是一个DolphinDB的内置函数(或内置函数的局部利用),一个DolphinDB的自定义函数,或者一个DolphinDB条件表达式,等等。这个代替计划为Orca带来了灵活性,用户能够按本人的须要,编写一段DolphinDB的脚本片段,而后,像pandas调用用户自定义函数一样,利用DolphinDB计算引擎执行这些脚本。
以下是将pandas承受可调用对象作为参数的代码改写为Orca代码的例子:
(1)求分组加权平均数
pandas:
wavg = lambda df: (df["prc"] * df["vol"]).sum() / df["vol"].sum() df.groupby("symbol").apply(wavg)
Orca:
df.groupby("symbol")["prc"].apply("wavg{,vol}")
Orca脚本通过apply函数,对group by之后的prc列调用了一个DolphinDB的局部利用wavg{,vol},转化为DolphinDB的脚本,等价于:
select wavg{,vol}(prc) from df group by symbol
将这个局部利用开展,等价于:
select wavg(prc,vol) from df group by symbol
(2)分组后按条件过滤
pandas:
df.groupby("symbol").filter(lambda x: len(x) > 1000)
Orca:
df.groupby("symbol").filter("size(*) > 1000")
上述例子的Orca脚本中,filter函数承受的字符串是一个过滤的条件表达式,转化为DolphinDB的脚本,等价于:
select * from df context by symbol having size(*) > 10000
即,filter的字符串呈现在了SQL的having语句中。
(3)对整个Series利用一个运算函数
pandas:
s.apply(lambda x: x + 1)
Orca:
s.apply("(x->x+1)")
pandas:
s.apply(np.log)
Orca:
s.apply("log")
罕用的计算函数,比方log, exp, floor, ceil, 三角函数,反三角函数等,Orca曾经集成。例如,求对数,通过s.log()即可实现。
(4)过滤时用逗号(,)代替&符号
DolphinDB的where表达式中,逗号示意执行程序,并且效率更高,只有在前一个条件通过后才会持续验证下一个条件。Orca对pandas的条件过滤进行了扩大,反对在过滤语句中用逗号:
pandas:
df[(df.x > 0) & (df.y < 0)]
Orca:
df[(df.x > 0), (df.y < 0)]
应用传统的&符号,会在最初生成DolphinDB脚本时将where表达式中的&符号转换为DolphinDB的and函数。而应用逗号,会在where表达式中的对应地位应用逗号,以达到更高的效率。
(5)如何实现DolphinDB的context by语句
DolphinDB反对context by语句,反对在分组内解决数据。在Orca中,这个性能能够通过groupby后调用transform实现。而transform通常须要用户提供一个DolphinDB自定义函数字符串。Orca对transform进行了扩大。对一个两头表达式调用groupby,并指定扩大参数lazy=True,而后不给定参数调用transform,则Orca会对调用groupby的表达式进行context by的计算。例如:
pandas:
df.groupby("date")["prc"].transform(lambda x: x.shift(5))
Orca的改写:
df.groupby("date")["id"].transform("shift{,5}")
Orca的扩大用法:
df.shift(5).groupby("date", lazy=True)["id"].transform()
这是Orca的一个特地的用法,它充分利用了惰性求值的劣势。在上述代码中,df.shift(5)并没有产生真正的计算,而只是生成了一个两头表达式(通过type(df.shift(5))会发现它是一个ArithExpression,而不是DataFrame)。如果指定了groupyby的扩大参数lazy=True,groupby函数就不会对表达式计算后的后果进行分组。
在动量交易策略教程中,咱们就充分利用了这个扩大性能,来实现DolphinDB的context by。
- 如果Orca目前无奈解决我的问题,我该怎么做?
本文解释了诸多Orca与pandas的差别,以及Orca的一些限度。如果你无奈躲避这些限度(比方,Orca的函数不反对某个参数,或者,apply一个简单的自定义函数,其中包含了第三方库函数调用,DolphinDB中没有这些性能),那么,你能够将Orca的DataFrame/Series通过to_pandas函数转化为pandas的DataFrame/Series,通过pandas执行计算后,将计算结果转换回Orca对象。
比方,Orca目前不反对rank函数的method=”average”和na_option=”keep”参数,如果你必须应用这些参数,你能够这么做:
>>> df.rank(method='average', na_option='keep') ValueError: method must be 'min' >>> pdf = df.to_pandas() >>> rank = pdf.rank(method='average', na_option='keep') >>> rank = orca.DataFrame(rank)
这样做能够解决你的问题,但它带来了额定的网络通信,同时,新的DataFrame的底层存储的表不再是原先的DataFrame所示意的表,因而无奈执行针对同一个DataFrame操作的一些优化。
Orca目前还处于开发阶段,咱们今后会为DolphinDB增加更丰盛的性能。届时,Orca的接口、反对的参数也会更欠缺。