- 登入
- 註冊
只要談到資料處理,python 基本上首當其衝,那關於 python 對於各式資料格式的處理方式,每個人都有各式各樣的作法,我們這篇文章就主要介紹兩種常見的資料處理方式-有序資料處理 List 及無序資料處理 Dictionary!
在使用 list 處理有序資料時,因為 python 對於 list 的作法並不是非常嚴謹,簡單來說,可以將各式各樣的東西存進list內,而且需要逐一照順序檢查的,才使用 list 。
因為論效能上 list 在 python 內的使用與其他語言的 array 其實不太一樣,所以在使用上會更吃效能些,那這邊就有序資料在使用上有幾個重點:
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筆了
假設我們今天對於資料的位置有需求,例如我要取出一串資料中,每隔五筆的加總,這時我們就會用上 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))
例如時間上(順序),計算上(最後五個資料的平均值)等等,更進階一點的有計算兩串資料在同位置上的狀況,這時候我們就會用上 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))
無序資料處理跟有序最大的情境區別在於取用方式,尤其是對於 key-value的資料情境有主要需求時,這類資料在未來的 Nosql、redis 等相關服務內都幾乎會走此概念,很多 web 資料傳的主要使用的 JSON 中,即便外部是 list,內部也會是 dictionary 的用法
這類情境通常發生在對於資料希望不靠順序可以直接取出來使用,並且藉由 key 名稱可以快速理解該資料的格式:
user_1 = {
"name": "alice",
"age": "25",
"balance": "13200",
}
# 在使用時除了不用管順序外,也能藉由 key名稱清楚知道資料內容為何
user_1['balance'] = int(user_1['balance']) + int(10000)
print(user_1)
很多資料都會有巢狀來建立類似資料夾內的資料分類的作法,這類作法在 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)
這類資料最長使用在如果資料內種類非常多,某些情境下只會更新某些資料,那就用這類方式處理即可。
例如上方的例子,如果資料進來後卻是 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 來用就可以,可讀性也大大提升,這對於需要重複使用的資料而言,會是一個很好的做法
今天的分享就到這邊,有任何問題歡迎下方留言提出討論囉!謝謝大家!