Python 資料格式處理與推薦方式-Python 進階處理(一)

只要談到資料處理,python 基本上首當其衝,那關於 python 對於各式資料格式的處理方式,每個人都有各式各樣的作法,我們這篇文章就主要介紹兩種常見的資料處理方式-有序資料處理 List 及無序資料處理 Dictionary!


追蹤量化通的粉絲專頁量化通QuantPass」即時獲取實用的資源!

一、有序資料處理 – List:

在使用 list 處理有序資料時,因為 python 對於 list 的作法並不是非常嚴謹,簡單來說,可以將各式各樣的東西存進list內,而且需要逐一照順序檢查的,才使用 list 。

因為論效能上 list 在 python 內的使用與其他語言的 array 其實不太一樣,所以在使用上會更吃效能些,那這邊就有序資料在使用上有幾個重點:

1. 資料必須有先後順序需求:

list 最常見的使用便是 append,可是 append是對整串資料的長度進行改變,換句話說,如果資料不斷新增,是不是會造成資料長度越來越長,那後續處理的效能就會越來越慢呢?

所以在這種情形下,我覺得限制資料長度其實是開發中一項很必要的思考邏輯:例如原本的程式碼可能是:

aaa = [i for i in range(10000)] # 舊資料只有10000筆

new_data = [d*200 for d in range(500000)] # 接下來要導入的 50萬筆新資料

for new in new_data: # 假設新資料要加入舊資料的情境

    aaa.append(new)

print(len(aaa))  # 得到的資料長度來到 15000筆

for check in new_data:
    if check > 10000 and check < 50000:
        print(check)

那如果今天需要新增的資料是 100 萬筆甚至 1000 萬筆呢?那這時後難道需要找尋與計算的只有最後 1000 筆資料,可是卻得要將 1000 萬筆都找過一次嗎?

這時候可以使用 切片(slice) 去做,但是即便是這樣,資料仍然是得先跑過 1000 萬筆後,才去切最後的 1000 筆,

所以良好的習慣上,其實是得在一開始儲存資料時就得進行管理:

aaa = [i for i in range(1000)] # 舊資料只有1000筆

new_data = [d*200 for d in range(500000)] # 接下來要導入的 50萬筆新資料

for new in new_data: # 假設新資料要加入舊資料的情境

    aaa.append(new)
    aaa.pop(0)

print(len(aa)) # 這樣長度就會維持在 1000筆了

2. 資料對於「位置」的使用有其必要性:

假設我們今天對於資料的位置有需求,例如我要取出一串資料中,每隔五筆的加總,這時我們就會用上 enumerate 這個內建函式囉:

tedata = [d for d in range(1, 500)]

new_d = []

for index, new in enumerate(tedata):
    if index % 5 == 0:
        new_d.append(tedata[index])


print(sum(new_d))

3. 資料具備一定程度上的關聯性:

例如時間上(順序),計算上(最後五個資料的平均值)等等,更進階一點的有計算兩串資料在同位置上的狀況,這時候我們就會用上 enumerate + zip的內建函式:

data_1 = [d*3 for d in range(1, 500)]
data_2 = [q*5 for q in range(1, 500)]

new_d = []

for index, d1d2 in enumerate(zip(data_1, data_2)):
    # 出來的格式會長成:
    # 0 (3, 7)
    # 1 (6, 14)
    # 2 (9, 21)
    # 前者是 index,  後者會是 tuple 的兩個同位置的數
    # 計算 能否被彼此整除
    # print(index, d1d2[0], d1d2[1])
    if d1d2[0] % 15 == 0 and d1d2[1] % 15 == 0:
        new_d.append(d1d2[0] + d1d2[1])

print(sum(new_d))

Py 101209161710
Py 101209161711

二、無序資料處理 – Dictionary:

無序資料處理跟有序最大的情境區別在於取用方式,尤其是對於 key-value的資料情境有主要需求時,這類資料在未來的 Nosql、redis 等相關服務內都幾乎會走此概念,很多 web 資料傳的主要使用的 JSON 中,即便外部是 list,內部也會是 dictionary 的用法

1. 對順序沒有需求,希望用 Key 滿足取用的情境:

這類情境通常發生在對於資料希望不靠順序可以直接取出來使用,並且藉由 key 名稱可以快速理解該資料的格式:

user_1 = {
    "name": "alice",
    "age": "25",
    "balance": "13200",
}
# 在使用時除了不用管順序外,也能藉由 key名稱清楚知道資料內容為何

user_1['balance'] = int(user_1['balance']) + int(10000)

print(user_1)

2. 對資料有巢狀層級的需求:

很多資料都會有巢狀來建立類似資料夾內的資料分類的作法,這類作法在 API call 資料時也很常遇到,

例如第一層可能是 api 回傳的狀態,第二層才是真實的資料,故可以靠這種模式快速做出層級切割:

api_back = {
    "code": 0,
    "status": 200,
    "data": [
        {
            "symbol": "BTCUSDT",
            "price": "30000",
            "amount": "3"
        },
        {
            "symbol": "ETHUSDT",
            "price": "2000",
            "amount": "5"
        },
    ]
}

# 找出該數據內,符合 symbol == 'ETHUSDT' 的資料:
for d in api_back['data']:
    if d['symbol'] == 'ETHUSDT':
        print(d)

3. 希望快速更改資料並使用key來增加可讀性:

這類資料最長使用在如果資料內種類非常多,某些情境下只會更新某些資料,那就用這類方式處理即可。

例如上方的例子,如果資料進來後卻是 list的格式,其實我們每次要使用時,還得用for迴圈才能檢查到,故在資料導入時,就可以直接把 list 替換成 dict的格式,之後就可以快速呼叫了:

# 我們新增一點長度增加複雜度:
api_back = {
    "code": 0,
    "status": 200,
    "data": [
        {
            "symbol": "BTCUSDT",
            "price": "30000",
            "amount": "3",
            "base": "USDT",
            "asset": "BTC",
            "vol": "32130",
            "status": True
        },
        {
            "symbol": "ETHUSDT",
            "price": "2000",
            "amount": "5",
            "base": "USDT",
            "asset": "ETH",
            "vol": "43436",
            "status": True
        },
        {
            "symbol": "ADAUSDT",
            "price": "10",
            "amount": "512",
            "base": "USDT",
            "asset": "ADA",
            "vol": "3414",
            "status": True
        },
        {
            "symbol": "BTCBUSD",
            "price": "30002",
            "amount": "2342",
            "base": "BUSD",
            "asset": "BTC",
            "vol": "7654",
            "status": True
        },
        {
            "symbol": "ETHBUSD",
            "price": "2002",
            "amount": "5",
            "base": "BUSD",
            "asset": "ETH",
            "vol": "3412",
            "status": True
        },
        {
            "symbol": "ADABUSD",
            "price": "10.1",
            "amount": "5",
            "base": "BUSD",
            "asset": "ADA",
            "vol": "546837428",
            "status": True
        },
    ]
}

再來,我們將其改成我們要的內容格式:

new_data = {}
# 在 api 回傳中 code = 0 or status = 200 通常表示資料有正常回傳
if api_back['code'] == 0 and api_back['status'] == 200:
    for k in api_back['data']:
        new_data[k['symbol']] = k

print(new_data)

結果如下:

{'BTCUSDT': {'symbol': 'BTCUSDT', 'price': '30000', 'amount': '3', 'base': 'USDT', 'asset': 'BTC', 'vol': '32130', 'status': True}, 'ETHUSDT': {'symbol': 'ETHUSDT', 'price': '2000', 'amount': '5', 'base': 'USDT', 'asset': 'ETH', 'vol': '43436', 'status': True}, 'ADAUSDT': {'symbol': 'ADAUSDT', 'price': '10', 'amount': '512', 'base': 'USDT', 'asset': 'ADA', 'vol': '3414', 'status': True}, 'BTCBUSD': {'symbol': 'BTCBUSD', 'price': '30002', 'amount': '2342', 'base': 'BUSD', 'asset': 'BTC', 'vol': '7654', 'status': True}, 'ETHBUSD': {'symbol': 'ETHBUSD', 'price': '2002', 'amount': '5', 'base': 'BUSD', 'asset': 'ETH', 'vol': '3412', 'status': True}, 'ADABUSD': {'symbol': 'ADABUSD', 'price': '10.1', 'amount': '5', 'base': 'BUSD', 'asset': 'ADA', 'vol': '546837428', 'status': True}}

在這樣的情形下,就可以藉由 print(new_data[‘BTCUSDT’)) 進而直接叫出我們要的資料,而不需要再用 for 迴圈了

最後,假設我們要的資料叫做要找出”ETHUSDT”, “ETHBUSD”兩個資料內 “price”的差值不為0的情況:

以下是原本資料的處理模式:

if api_back['code'] == 0 and api_back['status'] == 200:
    # 對 api_back['data'] 內的資料做改變
    ETH_p1 = 0
    ETH_p2 = 0

    for k in api_back['data']:
        if k['asset'] == 'ETH' and k['status']:
            if k['base'] == 'BUSD':
                ETH_p1 = float(k['price'])

            elif k['base'] == 'USDT':
                ETH_p2 = float(k['price'])


    if abs(ETH_p1 - ETH_p2) > 0:
        print("ETH got spread: {}".format(abs(ETH_p1 - ETH_p2)))

這是新的處理之後的資料,要做到一樣結果的方式:

# 目標要直接找出 "ETHUSDT", "ETHBUSD"
ETH_p1 = float(new_data['ETHUSDT']['price'])
ETH_p2 = float(new_data['ETHBUSD']['price'])
if abs(ETH_p1 - ETH_p2) > 0:
    print("ETH got spread: {}".format(abs(ETH_p1 - ETH_p2)))

因為已經知道目標,直接 call 來用就可以,可讀性也大大提升,這對於需要重複使用的資料而言,會是一個很好的做法

今天的分享就到這邊,有任何問題歡迎下方留言提出討論囉!謝謝大家!


量化通粉絲社群,一起討論程式交易!

加入LINE社群量化交易討論群」無壓力討論與分享!

加入臉書社團「程式交易 Taiwan」即時獲取實用的資源!

RoWay
RoWay

多年投資經驗的兩岸三地操盤手,曾任海外資產管理公司交易平台的產品經理、與各外商投資公司合作開發各式交易策略與系統。

擅長用Python執行資料蒐集、整理、分析與交易;也善於用Multicharts、MetaTrader等系統建構並回測期貨、期權、區塊鏈策略進而完成投資組合管理。

文章: 27

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *