乾貨丨Orca詳細入門指南

本文將詳細介紹Orca的安裝方法、基本操做,以及Orca相對pandas的差別,用戶在使用Orca編程時須要注意的細節,以便用戶能充分利用DolphinDB的優點,寫出高效的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)

2. 快速入門

經過傳入一列值建立一個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查看數據的索引、列名:ide

>>> 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")

3. 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對象所記錄的元數據產生了變化。

4. Orca的功能限制

因爲Orca的架構,Orca的接口有部分限制:

  • 列的數據類型

DolphinDB的表的每個列必須指定一種數據類型。DolphinDB的ANY類型不能做爲列的數據類型。所以,Orca的每個列不能包括混合的數據類型。此外,列中的數據也不容許是一個DolphinDB不支持的Python對象,例如Python內置的list, dict,或標準庫中的datetime等對象。

某些爲這些DolphinDB不支持的類型而設計的函數,例如DataFrame.explode,在Orca中就沒有實際意義。

  • 列名的限制

DolphinDB database的表中的列名必須是合法的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的內置函數、自定義函數或條件表達式等。詳細內容請參考高階函數一節。

5. 最佳實踐

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

6. 若是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的接口、支持的參數也會更完善。

相關文章
相關標籤/搜索