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

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

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

    電子郵件

    有興趣的主題
    量化交易金融知識台灣股市國內期貨海外期貨虛擬貨幣

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

    還有什麼詢問的?

    好富投 1920x400
    好富投 978x258

    點我了解更多資訊


    一、有序資料處理 – 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 來用就可以,可讀性也大大提升,這對於需要重複使用的資料而言,會是一個很好的做法

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

      電子郵件

      有興趣的主題
      量化交易金融知識台灣股市國內期貨海外期貨虛擬貨幣

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

      還有什麼詢問的?


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

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

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

      RoWay
      RoWay

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

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

      文章: 28

      發佈留言

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