Shopping Cart

購物車內沒有任何商品。

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

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

立即訂閱電子報,掌握最新資訊!

    稱呼

    電子郵件

    以下非必填,但若您願意分享,我們將能推送更精準的內容給您

    投資經驗

    是否為理工科背景、工程師或有寫程式的經驗?

    有興趣的主題
    量化交易台股期貨海外期貨虛擬貨幣美股

    有興趣的量化交易軟體/平台
    不清楚MultiChartsTradingViewPythonXQ

    想透過量化交易達成甚麼目的?
    不確定自動交易選股回測投資績效量化自己的投資方法想找現成的策略套用

    還有什麼想詢問的?

    20241111 1920 400
    20241111 978 258


    一、有序資料處理 – 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社群量化交易討論群」無壓力討論與分享!

    加入Discord 「量化交易討論群」即時獲取實用的資源!

    Write Together 101306261122
    Write Together 101306261121
    RoWay
    RoWay

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

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

    文章: 28

    發佈留言

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