P03 Practice: Accessing dict and list#
Q1. Accessing youbike data#
資料來源:(https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json)。
目的:使用該API可以取得當下所有YouBike站點的資訊,包括站點名稱、經緯度、可借車輛數、可停空位數等。我想要統計,目前全台北市的可借車輛數和可停空位數的總和,藉此來計算目前全台北市的YouBike使用率。此時我會需要用到Python中的List和Dictionary,來幫助我遍歷(traverse)YouBike資料,並進行加總計數。
擷取YouBike資料的步驟
利用
requests
庫中的.get()
方法來向指定的URL發送HTTP GET請求,以獲得該網址的回應。獲得的HTTP回應會存儲在
response
物件中,這個物件的.text
屬性包含了回應的文字內容,其資料型態為str
(字串)。因為我們知道返回的文字內容是用JSON格式儲存的,我們可以使用兩種方式將其轉換成Python的資料結構:
使用
response.json()
直接轉換。或是使用
json.loads(response.text)
,這會將JSON格式的字串轉換成Python的字典和列表結構。
一旦資料被轉換成Python的資料結構,我們就可以按照需求來存取其各個部分,通常這會是一個由多層的列表和字典組成的巢狀結構。
Getting Data#
這一節將教授如何從網路上獲取資料並將其轉換成Python的字典(dict)和列表(list)結構。注意,不要太在意抓取資料的細節暫,未來會有更詳細的教學。這裡主要是使用一個實際例子來展示如何存取字典(dict)。
requests
是一個專門用來發送網路請求的Python函式庫。在這裡,我們使用.get()
方法來向指定的URL發送HTTP GET請求,並獲得回應。你可以使用type()
函數來查看response
的資料型態,它應該會返回<class 'requests.models.Response'>
。response
是儲存網路請求回應的物件,其中包含了很多有用的資訊。關於這個物件的更多細節,你可以參考 Python_request_response。response.text
:這個屬性包含了HTTP回應的主要文本內容。response.status_code
:這個屬性會返回HTTP狀態碼,這個碼可以告訴你請求是成功還是失敗,或者是其他情況。對狀態碼的完整列表,請參見 Wikipedia: Network Status Code。response.json()
:如果你事先知道返回的內容是以JSON格式儲存的,那麼你可以使用.json()
方法將其轉換為Python的字典和列表結構。JSON(JavaScript Object Notation)是一種廣泛用於JavaScript的資料格式,它的結構類似於Python的字典和列表,因此能夠很容易地轉換過來。
透過這些步驟,你就能夠從網路上獲取資料,並將其轉換為Python程式能夠操作的數據格式。
# import requests library to send web query
import requests
# import json library to parse json format
import json
response = requests.get('https://tcgbusfs.blob.core.windows.net/dotapp/youbike/v2/youbike_immediate.json')
print(type(response)) # <class 'requests.models.Response'>
print(type(response.text)) # <class 'str'>
data = response.json()
# data = json.loads(response.text) # Alternative way
print(type(data))
<class 'requests.models.Response'>
<class 'str'>
<class 'dict'>
Traversing JSON data:#
採用系統性的測試策略,逐步探索由列表(list)與字典(dict)所組成的資料結構。透過這兩個基本法則,我們可以有系統地探索和理解複雜的JSON資料結構:
法則一:用type()確認資料型態。
法則二:遇到列表型態(list)時,選擇第0筆資料作為出發點繼續深入探索。
法則三:遇到字典型態(dict)時,使用
dict.keys()
方法列出所有的鍵(key)。接著,不失一般性地,抽取你感興趣的鍵(key)作為下一步的追蹤目標,通常會第0個。特別注意,如果.keys()
的輸出顯示為一系列類似於識別碼(id)的鍵(例如,youbike資料),那通常意味著設計者使用這些id來映射對應的資料。在這種情況下,你同樣可以選擇第一個識別碼(id)作為下一步追蹤的起點。
print(type(data))
print(data.keys())
print(type(data['retVal']))
print(len(data['retVal']))
print(data['retVal'].keys())
print(data['retVal']['0001'])
print(data['retVal']['0001'].keys())
<class 'dict'>
dict_keys(['retCode', 'retVal'])
<class 'dict'>
369
dict_keys(['0001', '0002', '0004', '0005', '0006', '0007', '0008', '0009', '0010', '0011', '0012', '0014', '0016', '0017', '0018', '0019', '0021', '0022', '0024', '0025', '0026', '0027', '0028', '0029', '0030', '0031', '0032', '0033', '0034', '0035', '0036', '0039', '0040', '0041', '0042', '0043', '0044', '0045', '0046', '0047', '0048', '0049', '0050', '0051', '0052', '0053', '0054', '0055', '0056', '0057', '0058', '0059', '0060', '0061', '0062', '0063', '0064', '0065', '0066', '0067', '0068', '0069', '0070', '0071', '0072', '0073', '0074', '0075', '0076', '0077', '0078', '0079', '0081', '0082', '0083', '0084', '0085', '0086', '0087', '0088', '0089', '0090', '0091', '0092', '0093', '0094', '0095', '0096', '0097', '0098', '0099', '0100', '0102', '0103', '0104', '0105', '0106', '0107', '0109', '0110', '0111', '0112', '0113', '0114', '0115', '0116', '0117', '0118', '0119', '0120', '0122', '0123', '0125', '0126', '0127', '0128', '0129', '0130', '0131', '0133', '0134', '0135', '0136', '0137', '0138', '0139', '0140', '0141', '0142', '0143', '0145', '0147', '0148', '0149', '0150', '0151', '0152', '0153', '0154', '0155', '0156', '0157', '0158', '0159', '0161', '0162', '0163', '0164', '0165', '0166', '0167', '0168', '0169', '0170', '0171', '0172', '0173', '0175', '0176', '0177', '0178', '0179', '0180', '0181', '0182', '0183', '0184', '0186', '0187', '0188', '0189', '0190', '0191', '0192', '0193', '0194', '0195', '0196', '0197', '0201', '0202', '0203', '0204', '0205', '0206', '0207', '0208', '0209', '0210', '0211', '0212', '0213', '0214', '0215', '0216', '0217', '0218', '0219', '0220', '0221', '0222', '0223', '0224', '0225', '0226', '0227', '0228', '0230', '0231', '0232', '0233', '0234', '0235', '0236', '0238', '0240', '0241', '0242', '0243', '0244', '0245', '0246', '0247', '0248', '0249', '0250', '0251', '0252', '0253', '0254', '0256', '0257', '0258', '0259', '0260', '0261', '0262', '0263', '0264', '0265', '0266', '0267', '0268', '0269', '0270', '0271', '0272', '0273', '0274', '0275', '0276', '0277', '0278', '0279', '0280', '0281', '0282', '0283', '0284', '0285', '0286', '0287', '0288', '0289', '0290', '0291', '0292', '0293', '0294', '0295', '0296', '0297', '0298', '0299', '0300', '0301', '0302', '0303', '0304', '0305', '0307', '0308', '0309', '0310', '0311', '0312', '0313', '0314', '0315', '0316', '0317', '0318', '0319', '0320', '0322', '0323', '0324', '0325', '0326', '0327', '0328', '0329', '0330', '0332', '0333', '0334', '0335', '0336', '0337', '0338', '0339', '0340', '0341', '0342', '0344', '0345', '0346', '0347', '0348', '0349', '0350', '0351', '0352', '0354', '0355', '0356', '0357', '0358', '0360', '0361', '0362', '0363', '0364', '0365', '0366', '0367', '0368', '0369', '0371', '0372', '0373', '0374', '0376', '0377', '0379', '0380', '0381', '0382', '0383', '0385', '0386', '0387', '0388', '0389', '0390', '0391', '0392', '0393', '0394', '0395', '0396', '0397', '0398', '0400', '0401', '0402', '0403', '0404', '0405'])
{'sno': '0001', 'sna': '捷運市政府站(3號出口)', 'tot': '84', 'sbi': '74', 'sarea': '信義區', 'mday': '20221030185227', 'lat': '25.0408578889', 'lng': '121.567904444', 'ar': '忠孝東路/松仁路(東南側)', 'sareaen': 'Xinyi Dist.', 'snaen': 'MRT Taipei City Hall Stataion(Exit 3)-2', 'aren': 'The S.W. side of Road Zhongxiao East Road & Road Chung Yan.', 'bemp': '10', 'act': '1'}
dict_keys(['sno', 'sna', 'tot', 'sbi', 'sarea', 'mday', 'lat', 'lng', 'ar', 'sareaen', 'snaen', 'aren', 'bemp', 'act'])
Observing data#
實際觀察該JSON資料結構後,你可能會注意到以下幾個特點:
資料結構由三層字典(dictionary)構成。
'tot'
這個鍵(key)所對應的值(value)是一個字串。這是十分常見的,因為大多數開放資料來源會選擇以文字型態儲存資料以便網路傳輸。{'retCode':"", 'retValue':{ {'0001':{'sno': '0001', 'sna': '捷運市政府站(3號出口)', 'tot': '88', ...}, '0002':{'sno': '0002', 'sna': '捷運國父紀念館站(2號出口)', 'tot': '16', ...}, '0004':{'sno': '0004', 'sna': '市民廣場', 'tot': '32', ...}, } } }
Getting all keys and values#
使用.keys()
與.values()
與.items()
分別取出dict的內容
data["retVal"].keys()
# data["retVal"].values()
dict_keys(['0001', '0002', '0004', '0005', '0006', '0007', '0008', '0009', '0010', '0011', '0012', '0014', '0016', '0017', '0018', '0019', '0021', '0022', '0024', '0025', '0026', '0027', '0028', '0029', '0030', '0031', '0032', '0033', '0034', '0035', '0036', '0039', '0040', '0041', '0042', '0043', '0044', '0045', '0046', '0047', '0048', '0049', '0050', '0051', '0052', '0053', '0054', '0055', '0056', '0057', '0058', '0059', '0060', '0061', '0062', '0063', '0064', '0065', '0066', '0067', '0068', '0069', '0070', '0071', '0072', '0073', '0074', '0075', '0076', '0077', '0078', '0079', '0081', '0082', '0083', '0084', '0085', '0086', '0087', '0088', '0089', '0090', '0091', '0092', '0093', '0094', '0095', '0096', '0097', '0098', '0099', '0100', '0102', '0103', '0104', '0105', '0106', '0107', '0109', '0110', '0111', '0112', '0113', '0114', '0115', '0116', '0117', '0118', '0119', '0120', '0122', '0123', '0125', '0126', '0127', '0128', '0129', '0130', '0131', '0133', '0134', '0135', '0136', '0137', '0138', '0139', '0140', '0141', '0142', '0143', '0145', '0147', '0148', '0149', '0150', '0151', '0152', '0153', '0154', '0155', '0156', '0157', '0158', '0159', '0161', '0162', '0163', '0164', '0165', '0166', '0167', '0168', '0169', '0170', '0171', '0172', '0173', '0175', '0176', '0177', '0178', '0179', '0180', '0181', '0182', '0183', '0184', '0186', '0187', '0188', '0189', '0190', '0191', '0192', '0193', '0194', '0195', '0196', '0197', '0201', '0202', '0203', '0204', '0205', '0206', '0207', '0208', '0209', '0210', '0211', '0212', '0213', '0214', '0215', '0216', '0217', '0218', '0219', '0220', '0221', '0222', '0223', '0224', '0225', '0226', '0227', '0228', '0230', '0231', '0232', '0233', '0234', '0235', '0236', '0238', '0240', '0241', '0242', '0243', '0244', '0245', '0246', '0247', '0248', '0249', '0250', '0251', '0252', '0253', '0254', '0256', '0257', '0258', '0259', '0260', '0261', '0262', '0263', '0264', '0265', '0266', '0267', '0268', '0269', '0270', '0271', '0272', '0273', '0274', '0275', '0276', '0277', '0278', '0279', '0280', '0281', '0282', '0283', '0284', '0285', '0286', '0287', '0288', '0289', '0290', '0291', '0292', '0293', '0294', '0295', '0296', '0297', '0298', '0299', '0300', '0301', '0302', '0303', '0304', '0305', '0307', '0308', '0309', '0310', '0311', '0312', '0313', '0314', '0315', '0316', '0317', '0318', '0319', '0320', '0322', '0323', '0324', '0325', '0326', '0327', '0328', '0329', '0330', '0332', '0333', '0334', '0335', '0336', '0337', '0338', '0339', '0340', '0341', '0342', '0344', '0345', '0346', '0347', '0348', '0349', '0350', '0351', '0352', '0354', '0355', '0356', '0357', '0358', '0360', '0361', '0362', '0363', '0364', '0365', '0366', '0367', '0368', '0369', '0371', '0372', '0373', '0374', '0376', '0377', '0379', '0380', '0381', '0382', '0383', '0385', '0386', '0387', '0388', '0389', '0390', '0391', '0392', '0393', '0394', '0395', '0396', '0397', '0398', '0400', '0401', '0402', '0403', '0404', '0405'])
Iterating data#
嘗試用for
-loop將所有的sbi
值給列印出來,請觀察看看不使用.items()
和使用.items()
的用法有何差異。
for k in data["retVal"].keys():
print(data["retVal"][k]["sbi"])
for k in data["retVal"]:
print(data["retVal"][k]["sbi"])
for k in data["retVal"].items():
print(k[1]["sbi"])
for k, v in data['retVal'].items():
print(k, v["sbi"])
# For observation: Convert the dict_keys to list for slicing the first 10 elements
for key in list(data['retVal'].keys())[:10]:
print(key,
data['retVal'][key]['sna'],
data['retVal'][key]['sbi'],
data['retVal'][key]['tot'])
0001 捷運市政府站(3號出口) 74 84
0002 捷運國父紀念館站(2號出口) 4 16
0004 市民廣場 0 32
0005 興雅國中 1 10
0006 臺北南山廣場 11 54
0007 信義廣場(台北101) 2 20
0008 世貿三館 2 42
0009 松德站 6 20
0010 台北市災害應變中心 4 12
0011 三張犁 2 16
Q2. Sum-up total number of sbi and tot#
我想要知道,在全天候24小時中,ubike的使用率何時較高、何時較低。所以我打算統計全台北市所有YouBike站點的可借車輛數(sbi)和可停空位數(tot)的總和,並計算兩者的比例。這樣一來,我就可以知道該時刻全台北市的YouBike使用率了。未來,我只需要寫一隻爬蟲,每五分鐘就收一次YouBike的資料,並計算出YouBike的使用率,就可以知道全天候24小時中,YouBike的使用率何時較高、何時較低了。
所以接下來,我打算計算 data['retVal']
中所有 sbi
和 tot
值的總和,以及這兩個總和的比例:
Initialize variables
sbi_sum
和tot_sum
,都設為0。這兩個變數將用於累加特定的數值。Traversing data
資料使用for
迴圈遍歷data['retVal']
字典的所有鍵(key
)。sbi_sum
累加每個key
對應的sbi
值(需先轉換為整數)。tot_sum
累加每個key
對應的tot
值(需先轉換為整數)。
Sum-up data
最後,返回三個值:sbi_sum
(sbi
的總和)、tot_sum
(tot
的總和)以及sbi_sum/tot_sum
(sbi
總和與tot
總和的比例)。
sbi_sum = 0
tot_sum = 0
for key in data['retVal'].keys():
sbi_sum += int(data['retVal'][key]['sbi'])
tot_sum += int(data['retVal'][key]['tot'])
sbi_sum, tot_sum, sbi_sum/tot_sum
(2249, 6282, 0.3580070041388093)
Q3: Counting ubike site number of each area#
現在,我們已經可以用簡單的for-loop逐一跑過所有的Youbike即時資料。但我發現在資料中,area
欄位資料為行政區,所以我想要統計,每個行政區有幾個Youbike站點。這樣一來,我就可以知道哪個行政區的Youbike站點最多了。相同的,我也可以統計,每個行政區的Youbike站點的可借車輛數(sbi)和可停空位數(tot)的總和,並計算兩者的比例。這樣一來,我就可以知道哪個行政區的Youbike使用率最高了。
Counting sites by area#
area_dict = {}
for key in data['retVal'].keys():
if data['retVal'][key]['sarea'] not in area_dict:
area_dict[data['retVal'][key]['sarea']] = 1
else:
area_dict[data['retVal'][key]['sarea']] += 1
for k, n in area_dict.items():
print(k, n)
信義區 38
大安區 48
中山區 46
松山區 34
南港區 22
中正區 34
萬華區 22
文山區 27
大同區 22
士林區 36
內湖區 42
北投區 28
Method 2#
我可以用items()
一次取出一對key-value pair,但因為是(key, value)的型態,所以我for-loop前面要用兩個參數去接每一對數值,我將之命名為key, row
。如果這樣寫,if-else中的表示式就可以跟著改。我不用一定要用key
才能存取dictionary中的資料,我可以直接用後面的row,也就是key所對應到的value來取用dictionary中的資料。
area_dict = {}
for key, row in data["retVal"].items():
if row["sarea"] not in area_dict:
area_dict[row["sarea"]] = 1
else:
area_dict[row["sarea"]] += 1
area_dict
{'信義區': 38,
'大安區': 48,
'中山區': 46,
'松山區': 34,
'南港區': 22,
'中正區': 34,
'萬華區': 22,
'文山區': 27,
'大同區': 22,
'士林區': 36,
'內湖區': 42,
'北投區': 28}
Q4: Traversing AQI data#
以下為存取環保署開放資料平台空品資料的API連結與方法,請仿照Trace Youbike data的方法依序回答以下兩個問題。
import requests
import json
url = "https://airtw.epa.gov.tw/gis_ajax.aspx?Type=GetAQInfo&Layer=EPA&QueryTime=2023/09/17%2011:00"
aqdata = requests.get(url).json()
print(type(aqdata))
<class 'list'>
AQI.1 Accessing list#
請Access該筆資料列印出第一個站台的站台名稱為何?
aqdata[0]['SiteName']
'屏東(建興)'
AQI.2 Checking data type#
請Access空品資料的任一筆資料,觀察資料內容,用程式列印出AQI的Data type為何?
type(aqdata[0]['AQI'])
str
AQI.3: Converting data structure#
目前的資料是將所有測站資料存放在一個List中,這樣會使得我們無法查詢例如「三重」測站的資料。因此,我們需要將資料結構轉換成Dictionary,以便我們可以透過測站名稱來查詢資料。怎麼做?
for site in aqdata:
if site['SiteName'] == '三重':
print(site['AQI'])
49
aqdict = {}
for site in aqdata:
aqdict[site['SiteName']] = site['AQI']
print(aqdict['三重'])
54