本文將詳細介紹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: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")
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的接口有部分限制:
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
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中的一列就可能包含混合類型的數據。
DolphinDB Python API目前沒法解析Python函數,所以,例如DataFrame.apply, DataFrame.agg等函數沒法接受一個Python可調用對象做爲參數。
對於這個限制,Orca提供了一個備選方案:傳入一個DolphinDB字符串,它能夠是DolphinDB的內置函數、自定義函數或條件表達式等。詳細內容請參考高階函數一節。
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採用了惰性求值策略,某些操做不會馬上在服務端計算,而是轉化成一箇中間表達式,直到真正須要時才發生計算。須要觸發計算時,用戶應調用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裏的列,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與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的接口、支持的參數也會更完善。