返回文章列表

Python資料集合操作技巧

本文探討 Python 內建資料集合 List、Tuple 和字典的特性與操作技巧,包含如何使用索引、切片、`in` 運算子、lambda 函式以及生成器表示式等方法,有效率地處理和分析資料,並示範如何將這些技巧應用於實際案例,例如解析 HTTP Headers 和處理年份與乳酪消費量資料。

Python 資料處理

Python 提供了多種內建資料集合,其中 List、Tuple 和字典是最常用的型別。List 作為可變序列,允許修改元素,適用於需要動態調整資料的場景。Tuple 則為不可變序列,適用於固定結構的資料,例如座標或 HTTP Headers。字典則提供了鍵值對映的功能,方便快速查詢和管理資料。理解這些資料集合的特性和操作技巧,對於提升 Python 資料處理效率至關重要。常見操作包含使用索引和切片擷取特定資料區段、使用 in 運算子檢查元素是否存在,以及使用 lambda 函式和生成器表示式簡化程式碼並提升效能。這些技巧在處理實際資料,例如分析年份和乳酪消費量的關係時,能發揮重要作用,有效地提取和分析資訊。

資料蒐集與整理

在前面的章節中,我們已經介紹了一些資料集合。現在是時候深入瞭解這些集合是什麼,以及如何有效地使用它們。正如我們在第一章《我們的間諜工具包》中所觀察到的,Python 提供了多種不同型別的數字。常用的數字是內建的,而更專業的數字則需要從標準函式庫中匯入。

同樣地,Python 有許多內建的集合型別。此外,標準函式庫中還提供了大量的額外集合型別。我們將重點介紹內建的列表(list)、元組(tuple)、字典(dictionary)和集合(set)。這些涵蓋了處理資料群組的基本需求。

使用 Python 列表

Python 的列表類別可以被概括為一個可變序列。可變性意味著我們可以新增、更改和移除專案(列表可以被更改)。序列意味著專案是根據它們在列表中的位置來存取的。

語法非常簡單;我們把資料專案放在 [] 中,並用 , 分隔專案。我們可以在序列中使用任何 Python 物件。

總公司需要有關選定乳酪品種的人均消費量的資訊。雖然總公司並未向現場特工透露太多資訊,但我們知道他們經常想了解自然資源和戰略經濟優勢。

我們可以在 http://www.ers.usda.gov/datafiles/Dairy_Data/chezcon_1_.xls 找到乳酪消費資料。遺憾的是,資料採用專有的試算表格式,難以處理。要自動化資料收集,我們需要像 Project Stingray 這樣的工具來從檔案中提取資料。對於手動資料收集,我們可以複製和貼上資料。

以下是從 2000 年開始到 2010 年的資料;我們將用它來展示一些簡單的列表處理:

>>> cheese = [29.87, 30.12, 30.60, 30.66, 31.33, 32.62, 32.73, 33.50, 32.84, 33.02]
>>> len(cheese)
10
>>> min(cheese)
29.87
>>> cheese.index(max(cheese))
7

內容解密:

  1. cheese = [29.87, 30.12, 30.60, 30.66, 31.33, 32.62, 32.73, 33.50, 32.84, 33.02]:建立一個名為 cheese 的列表物件,並將其指定給變數 cheese
  2. len(cheese):使用 len() 函式取得列表中的專案數量,結果為 10。
  3. min(cheese):使用 min() 函式找出列表中的最小值,結果為 29.87。
  4. cheese.index(max(cheese)):先使用 max() 函式找出列表中的最大值,然後使用 index() 方法找出該最大值在列表中的索引,結果為 7。

我們建立了一個列表物件並將其指定給 cheese 變數。使用 min() 函式可以找出序列中的最小值。index() 方法則是在序列中搜尋匹配的值。我們發現,使用 max() 函式找到的最大消費量的索引是 7,對應到 2007 年。之後,乳酪的消費量略有下降。

注意,我們使用了字首函式表示法(min()max()len() 等)和方法函式表示法(cheese.index() 等)。Python 提供了豐富多樣的表示法,並不拘泥於只使用方法函式。

由於列表是可變的,我們可以向列表中追加額外的值。我們可以使用 cheese.extend() 方法來擴充套件給定的列表:

>>> cheese.extend([32.92, 33.27, 33.51])
>>> cheese
[29.87, 30.12, 30.6, 30.66, 31.33, 32.62, 32.73, 33.5, 32.84, 33.02, 32.92, 33.27, 33.51]

內容解密:

  1. cheese.extend([32.92, 33.27, 33.51]):使用 extend() 方法將新的列表 [32.92, 33.27, 33.51] 新增到原有的 cheese 列表中。
  2. 輸出結果為 [29.87, 30.12, 30.6, 30.66, 31.33, 32.62, 32.73, 33.5, 32.84, 33.02, 32.92, 33.27, 33.51],顯示了更新後的 cheese 列表。

我們也可以使用 +運算元來合併兩個列表。

使用列表索引操作

我們可以使用 cheese[index] 表示法來存取個別專案:

>>> cheese[0]
29.87
>>> cheese[1]
30.12

內容解密:

  1. cheese[0]:存取 cheese 列表中的第一個專案,結果為 29.87
  2. cheese[1]:存取 cheese 列表中的第二個專案,結果為 30.12

這允許我們從列表中挑選特定的專案。由於列表已經排序,第一個專案是最小值,第二個專案是次小值。我們也可以從列表末尾開始倒數索引:

>>> cheese[-2]
33.5
>>> cheese[-1]
33.51

內容解密:

  1. cheese[-2]:存取 cheese 列表中倒數第二個專案,結果為 33.5
  2. cheese[-1]:存取 cheese 列表示中的最後一個專案,結果為 33.51

在排序後的資料中,-2 對應到次大值,而 -1 對應到最大值。在原始的未排序資料中,cheese[-2] 將對應到2009年的資料。

切片操作

我們也可以從列表中擷取切片。一些常見的切片操作如下所示:

>>> cheese[:5]
[29.87, 30.12, 30.6, 30.66, 31.33]
>>> cheese[5:]
[32.62, 32.73, 32.84, 32.92, 33.02, 33.27, 33.5, 33.51]

內容解密:

1. cheese[:5]:擷取前五個元素,即乳酪消費量最小的五個值。 2. cheese[5:]:擷取從第六個元素開始到結尾的所有元素,即乳酪消費量較大的值。

使用in運算子進行比較

在處理集合時,我們發現有一個新的比較運算子——in。我們可以使用簡單的in測試來檢視一個值是否出現在集合中的任何地方:

>>>30 .5 in cheese 
False 
>>>33 .5 in cheese 
True 

內容解密:

1. in運算子檢查某個值是否存在於集合中,例如檢查特定數字是否在乳酪資料中。 2. True或False代表檢查結果,表示該值是否存在於序列中。

使用Python Tuple的實務應用

Python的tuple類別可簡要地描述為不可變序列(immutable sequence)。不可變意味著一旦建立,tuple就不能被改變。數值3是不可變的,它始終是3。序列意味著其中的專案是根據它們在tuple中的位置來存取的。

Tuple的基本語法與特性

Tuple的語法非常簡單;我們需要將資料專案放在()中,並用,分隔這些專案。我們可以在序列中使用任何Python物件。其理念是建立一個看起來像數學座標的物件,例如(3, 4)

Tuple在Python內部被廣泛使用。當我們使用多重指定時,例如以下程式碼的右側建立了一個tuple,而左側則將其分解:

power, value = 0, 1

右側建立了一個二元tuple (0, 1)。語法不需要在tuple周圍加上()。左側則分解了一個二元tuple,將值賦給兩個不同的變數。

Tuple的實務應用場景

我們通常使用tuple來表示那些元素數量由問題域(problem domain)固定的資料物件。我們經常使用tuple來表示座標對,例如緯度和經度。由於tuple的大小不能改變,因此我們不需要像list那樣具有靈活的長度。對於一個應該只有兩個值(緯度和經度)的tuple來說,三元tuple代表什麼意義?不同的問題可能涉及經度、緯度和海拔;在這種情況下,我們使用的是三元tuple。在這些例子中使用二元tuple或三元tuple是問題的基本特徵:我們不需要改變物件來新增或刪除值。

HTTP Headers中的Tuple應用

當我們檢視requests和responses中的HTTP headers時,我們看到它們被表示為一個二元tuple的list,例如('Content-Type', 'text/html; charset=utf-8')。每個tuple都有一個header名稱(‘Content-Type’)和header值(’text/html; charset=utf-8’)。

年份與乳酪消費量的Tuple應用

這裡有一個使用二元tuple來包含年份和乳酪消費量的例子:

year_cheese = [(2000, 29.87), (2001, 30.12), (2002, 30.6), (2003, 30.66),
               (2004, 31.33), (2005, 32.62), (2006, 32.73), (2007, 33.5),
               (2008, 32.84), (2009, 33.02), (2010, 32.92), (2011, 33.27),
               (2012, 33.51)]

對Tuple List進行分析

這種list-of-tuple結構允許我們對資料進行稍微簡單的分析。以下是兩個例子:

>>> max(year_cheese, key=lambda x: x[1])
(2012, 33.51)
>>> min(year_cheese, key=lambda x: x[1])
(2000, 29.87)

我們對我們的tuple list應用了max()函式。max()函式的第二個引數是另一個函式——在這個例子中,是一個匿名lambda物件——它評估每個tuple中的第二個值。

使用Lambda函式與命名函式

以下是兩個更多的例子,展示了lambda物件的行為:

>>> (2007, 33.5)[1]
33.5
>>> (lambda x: x[1])((2007, 33.5))
33.5

(2007, 33.5)這個二元tuple具有[1] get item操作;這將選取位置1上的專案,即33.5。位置0上的專案是年份2007。

(lambda x: x[1])表示式建立了一個匿名lambda函式。然後,我們可以將這個函式應用於(2007, 33.5)這個二元tuple。由於x[1]表示式選取索引位置1上的專案,因此我們得到33.5這個值。

#### 內容解密:

  • max()min()函式用於找出list中元素的最大值和最小值。
  • key引數指定了一個函式,用於在比較元素之前對它們進行轉換。
  • 在這個例子中,lambda x: x[1]是一個匿名函式,它傳回每個tuple的第二個元素。
  • 這使得max()min()能夠根據tuple的第二個元素(乳酪消費量)來找出最大值和最小值。

使用命名函式替代Lambda

我們可以建立一個完全定義的、命名的函式,而不是使用lambda,如下所示:

def by_weight(yr_wt_tuple):
    year, weight = yr_wt_tuple
    return weight

命名函式有兩個優點:它有名稱,並且可以有多行程式碼。Lambda函式的優點是當整個函式可以簡化為單一表達式時,它非常簡潔。

#### 內容解密:

  • by_weight函式接受一個二元tuple作為輸入,並傳回第二個元素(乳酪消費量)。
  • 這使得我們能夠根據乳酪消費量對tuple list進行排序。
  • sorted()函式使用by_weight函式作為key引數,對tuple list進行排序。

對Tuple List進行排序

我們可以使用這種技術對這些二元tuple進行排序,使用函式而不是lambda,如下所示:

>>> by_cheese = sorted(year_cheese, key=by_weight)
>>> by_cheese
[(2000, 29.87), (2001, 30.12), (2002, 30.6), (2003, 30.66), (2004, 31.33),
 (2005, 32.62), (2006, 32.73), (2008, 32.84), (2010, 32.92), (2009, 33.02),
 (2011, 33.27), (2007, 33.5), (2012, 33.51)]

#### 內容解密:

  • sorted()函式傳回一個新的list,包含原始list中的元素,但已排序。
  • key引數指定了一個函式,用於在比較元素之前對它們進行轉換。
  • 在這個例子中,by_weight函式用於根據乳酪消費量對tuple list進行排序。

使用生成器表示式處理Tuple List

如果我們想要根據給定的年份找到乳酪產量,我們需要在這個二元tuple序列中搜尋匹配的年份。我們不能簡單地使用list.index()函式來定位專案,因為我們只使用了專案的一部分。一種策略是使用生成器表示式從list中提取年份,如下所示:

>>> years = [item[0] for item in year_cheese]
>>> years
[2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012]

#### 內容解密:

  • 生成器表示式用於從year_cheese list中提取每個tuple的第一個元素(年份)。
  • 結果被收集到一個新的list中,並賦給years變數。
  • 這使得我們能夠根據年份來搜尋對應的乳酪消費量。

使用Python字典對映取得智慧資料

在處理來自RESTful API的資料時,Python字典是一種非常有用的資料結構。字典包含從鍵到值的對映,可以被視為一個可變的對映結構。這意味著我們可以新增、更改和刪除其中的專案。

字典的基本語法和特性

字典的語法非常簡單,我們使用大括號 {} 將鍵值對包裹起來,鍵和值之間使用冒號 : 分隔,不同的鍵值對之間使用逗號 , 分隔。值可以是任何型別的Python物件,而鍵則必須是不可變的物件,如字串、數字或元組。

示例:建立和存取字典

spot_rate = {'currency': 'EUR', 'amount': '361.56'}
print(spot_rate['currency'])  # 輸出:EUR
print(spot_rate['amount'])    # 輸出:361.56

import decimal
print(decimal.Decimal(spot_rate['amount']))  # 輸出:Decimal('361.56')

在這個例子中,我們建立了一個名為 spot_rate 的字典,並存取了其中的鍵 currencyamount 對應的值。

修改字典中的值

由於字典是可變的,我們可以輕易地更改與鍵相關聯的值。

form = {"currency": "EUR"}
form['currency'] = "USD"
print(form)  # 輸出:{'currency': 'USD'}

內容解密:

  1. 建立一個包含單一鍵值對的字典 form
  2. form 中鍵 currency 的值從 "EUR" 修改為 "USD"
  3. 列印修改後的 form 字典。

安全地取得值

當嘗試取得一個可能不存在的鍵的值時,可以使用 dict.get(key, default) 方法來避免 KeyError 異常。

print(spot_rate.get('amount'))  # 輸出:'361.56'
print(spot_rate.get('oops'))    # 輸出:None
print(spot_rate.get('oops', '#Missing'))  # 輸出:'#Missing'

內容解密:

  1. 使用 get() 方法取得鍵 amount 的值,如果存在則傳回其值。
  2. 嘗試取得不存在的鍵 oops 的值,由於未提供預設值,因此傳回 None
  3. 再次嘗試取得鍵 oops 的值,這次提供了預設值 '#Missing',因此傳回該預設值。

使用字典的其他方法

字典還提供了其他有用的方法,如 keys(), values(), 和 items()

print(spot_rate.keys())   # 輸出:dict_keys(['amount', 'currency'])
print(spot_rate.items())  # 輸出:dict_items([('amount', '361.56'), ('currency', 'EUR')])
print(spot_rate.values()) # 輸出:dict_values(['361.56', 'EUR'])

內容解密:

  1. keys() 方法傳回一個包含字典所有鍵的檢視物件。
  2. items() 方法傳回一個包含字典所有鍵值對的檢視物件,每個鍵值對以元組形式表示。
  3. values() 方法傳回一個包含字典所有值的檢視物件。

將列表轉換為字典

我們可以使用 dict() 函式將一個由二元組組成的列表轉換為字典。

rate_as_list = list(spot_rate.items())
print(rate_as_list)  # 輸出:[('amount', '361.56'), ('currency', 'EUR')]
print(dict(rate_as_list))  # 輸出:{'amount': '361.56', 'currency': 'EUR'}

內容解密:

  1. spot_rate 字典的鍵值對轉換為一個列表 rate_as_list
  2. 使用 dict() 函式將 rate_as_list 轉換回字典。