干货丨Orca入门指南

本文将详细介绍Orca的安装方法、基本操做,以及Orca相对pandas的差别,用户在使用Orca编程时须要注意的细节,以便用户能写出高效的Orca代码。数据库

  1. 安装

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)
  1. 快速入门

经过传入一列值建立一个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:app

>>> 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了:dom

>>> 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")
  1. 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对象所记录的元数据产生了变化。

  1. 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的内置函数、自定义函数或条件表达式等。详细内容请参考高阶函数一节。

  1. 最佳实践

  • 减小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。

  1. 若是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操做的一些优化。

相关文章
相关标签/搜索