2021年10月12日 星期二

Python 學習筆記之 Pandas (一) : Series 物件

最近在看機器學習時發現資料前處理都要用到 Pandas, 所以打算先把 Pandas 好好測試過後再繼續 ML 的學習. Python 的資料科學套件中 Pandas 最實用, 雖然機器學習少不了它, 但它其實一開始是為了金融用途而設計的, 特別是針對金融資訊中的時間序列資料進行操控.  
 
Pandas 是 Wes McKinney 在 2008 年於 AQR 資本管理公司工作時所開發的財務分析工具, 於 2009 年開放原始碼. Pandas 的主要目的是讓使用者能快速發現資料中的資訊與隱藏的意義, 它吸納了 R 語言中的許多好用套裝軟體例如 plyr 與 reshape2 等, 補足了 Python 生態系在資料分析與建模上的缺口 , 參考 :


本系列測試參考書目如下 :

人工智慧 Python 基礎課 (碁峰, 陳會安)
Python 金融分析 第二版 (碁峰, 賴屹民譯)
Pandas 資料分析實戰 第二版 (博碩, 陳建宏譯)
Python 資料科學學習手冊 (碁峰, 何敏煌譯)

Pandas 整合了 Numpy, Scipy, 與 Matplotlib, 在整個 Python 資料科學技術堆疊中屬於最上層, 主要是透過其 DataFrame (資料框) 物件提供異質表格資料的讀取, 轉換, 與處理等資料操控 (data manipulation) 功能, 另外 Pandas 也提供統計分析函數, 並透過 Matplotlib 提供資料繪圖功能 : 




在 Python 資料科學與機器學習中主要負責資料清洗 (data cleansing) 的前處理作業. 基本上 Pandas 是用來操控結構化資料 (structured data, 即有固定欄位與資料模型的一維或二維資料), 但也提供了將非結構化資料轉成結構化資料的函數. 

資料依其組織型態可分為三種 : 
  • 結構化資料 : 
    具有固定欄位, 格式, 以及順序的資料, 它含有一個定義資料型別 (例如數值, 字串等) 與限制 (字元數, 最大最小值等) 的資料模型. 典型的結構化資料例如試算表與關連式資料庫中的資料表. 結構化資料可以用 SQL 語言查詢. 
  • 半結構化資料 :
    資料雖然有欄位但不固定 (不一致), 缺乏嚴格的資料模型, 例如 JSON 格式就是典型的半結構化資料, 雖然有些 JSON 資料有定義良好的格式. 但它沒有關聯式資料庫中的資料綱要 (schema) 可套用. 半結構化資料可用 NoSQL 語言查詢. 
  • 非結構化資料 :
    沒有嚴格定義欄位與型別的資料, 例如圖片, 視訊, 音訊, PDF, 網頁等等. Pasndas 無法直接操控非結構化資料, 但提供了從網頁中擷取特定內容的好用工具.  
參考 :



1. 安裝 Pandas :

Pandas 係第三方套件, 因此使用前需先安裝, 指令如下 : 

pip install pandas

如果已經安裝過可在後面添加 -U 參數安裝最新版 :

pip install pandas -U

Pandas 是建立在 Numpy 的基礎上, 因此若尚未安裝 Numpy 會自動安裝 Numpy 等相依套件. 安裝好後即可用 import 指令匯入 Pandas 套件. 一般慣例用 pd 作為其簡名, 呼叫 dir() 函數可檢視 Pandas 套件中的類別成員 (屬性與方法) : 

>>> import pandas as pd    
>>> dir(pd)   
['BooleanDtype', 'Categorical', 'CategoricalDtype', 'CategoricalIndex', 'DataFrame', 'DateOffset', 'DatetimeIndex', 'DatetimeTZDtype', 'ExcelFile', 'ExcelWriter', 'Flags', 'Float32Dtype', 'Float64Dtype', 'Float64Index', 'Grouper', 'HDFStore', 'Index', 'IndexSlice', 'Int16Dtype', 'Int32Dtype', 'Int64Dtype', 'Int64Index', 'Int8Dtype', 'Interval', 'IntervalDtype', 'IntervalIndex', 'MultiIndex', 'NA', 'NaT', 'NamedAgg', 'Period', 'PeriodDtype', 'PeriodIndex', 'RangeIndex', 'Series', 'SparseDtype', 'StringDtype', 'Timedelta', 'TimedeltaIndex', 'Timestamp', 'UInt16Dtype', 'UInt32Dtype', 'UInt64Dtype', 'UInt64Index', 'UInt8Dtype', '__builtins__', '__cached__', '__doc__', '__docformat__', '__file__', '__getattr__', '__git_version__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_config', '_hashtable', '_is_numpy_dev', '_lib', '_libs', '_np_version_under1p17', '_np_version_under1p18', '_testing', '_tslib', '_typing', '_version', 'api', 'array', 'arrays', 'bdate_range', 'compat', 'concat', 'core', 'crosstab', 'cut', 'date_range', 'describe_option', 'errors', 'eval', 'factorize', 'get_dummies', 'get_option', 'infer_freq', 'interval_range', 'io', 'isna', 'isnull', 'json_normalize', 'lreshape', 'melt', 'merge', 'merge_asof', 'merge_ordered', 'notna', 'notnull', 'offsets', 'option_context', 'options', 'pandas', 'period_range', 'pivot', 'pivot_table', 'plotting', 'qcut', 'read_clipboard', 'read_csv', 'read_excel', 'read_feather', 'read_fwf', 'read_gbq', 'read_hdf', 'read_html', 'read_json', 'read_orc', 'read_parquet', 'read_pickle', 'read_sas', 'read_spss', 'read_sql', 'read_sql_query', 'read_sql_table', 'read_stata', 'read_table', 'reset_option', 'set_eng_float_format', 'set_option', 'show_versions', 'test', 'testing', 'timedelta_range', 'to_datetime', 'to_numeric', 'to_pickle', 'to_timedelta', 'tseries', 'unique', 'util', 'value_counts', 'wide_to_long']

可用 __version__ 檢視目前的 Pandas 版本 :

>>> pd.__version__    
'1.2.5' 

Pandas 是建立在 Numpy 之上的新套件, 可以說是 Numpy 陣列結構的加強版, 主要是為列與欄的索引加上了標籤, 並提供了 Series, DataFrame 與 Panel 這三種資料結構物件來裡裡資料, 在資料科學與機器學習中 Panas 主要是負責資料清理或資料前處理工作, 其中 Series 物件是具有索引標籤的一維向量; DataFrame 是具有行列索引標籤的二維陣列 (矩陣), 而 Panel 則是三個軸都有索引標籤的三維陣列, 結構如下 : 




由於 DataFrame 的階層式索引功能也可以用來表示三維陣列, 因此 Panel 的應用並不多, 也使 DataFrame 成為 Pandas 最常用的資料結構. 


2. 建立 Series 物件 :   

Series 物件其實就是在 Numpy 的一維陣列上添加 (列) 索引標籤, Numpy 的 ndarray 陣列只能用 0 起始的整數當索引, 而 Series 物件則除了原本的整數索引外, 還可以用字串標籤當索引. 我們可以把 Series 物件看成是一個單行向量 (column vector), 其索引標籤就是行向量的列編號或名稱.  

建立 Series 物件的語法如下 : 

pd.Series(data [, index])      

其中必要參數 data 可以是任何 Python 資料型態 (數值, 字串, 元組串列, 元組, 字典, 物件) 或 Numpy 的 ndarray 陣列等資料類型 (各元素的資料型態必須相同, 因為它們是同一欄變數之值), 備選參數 index 用來指定資料的索引, 可以是串列, 元組, 或字典等資料類型. 

Series 物件常用的屬性如下表 : 

 
 Series 物件屬性  說明
 index  索引 (Index 物件)
 values  內容 (數值, 字串, 元組串列, 元組, 字典, 物件或 Numpy 陣列)
 loc  索引位置物件, 用索引標籤來存取元素, 例如 s.loc['a']
 iloc  索引位置物件, 用 0 起始整數索引存取元素, 例如 s.loc[0]
 dtype  Series 物件內容的資料型態
 shape  Series 物件內容的外形
 size  Series 物件元素數目
 name  Series 物件名稱


其中最常用的是 index 與 values 屬性, 可用來存取其標籤索引與內容 (值). 屬性 loc 是一個 LocIndexer 物件, 用來儲存索引標籤與對應的值, 可用 ['索引標籤'] 來存取元素值. 屬性 iloc 是一個 iLocIndexer 物件, 用來儲存 0 起始整數索引與對應的值, 可用 [整數索引] 來存取元素值. 其他屬性如 stype, shape, size 等皆來自 Numpy, 與 ndarray 物件之用法相同. 

由於 Pandas 內部資料結構直接使用 Numpy 的 ndarray 物件, 因此 Series  物件的方法也與 ndarray 物件相同, 常用的方法如下表 :


 Series 物件常用方法 說明
 append() 串接兩個或多個 Series 物件
 corr() 計算兩個 Series 物件的相關性
 cov() 計算兩個 Series 物件的變異數
 describe() 計算並輸出 Series 物件的描述統計值
 drop_duplicates() 刪除重複元素後傳回新的 Series 物件
 equals() 比較兩個 Series 物件元素是否相同 (傳回 True/False)
 get_values() 傳回 Series 物件之值 (與 values 屬性功能相同)
 hist() 繪製 Series 物件之 histogram (直方圖)
 isin() 檢查元素是否在 Series 物件中  (傳回 True/False)
 min() 傳回 Series 物件中最小的元素
 max() 傳回 Series 物件中最大的元素
 mean() 傳回 Series 物件所有元素的平均值
 median() 傳回 Series 物件所有元素的中位數
 mode() 傳回 Series 物件中出現次數最多者 (mode)
 quantile() 傳回 Series 物件的指定四分位數
 replace() 以指定的值取代 Series 物件中的某值
 sample() 隨機傳回 Series 物件中的一個元素值
 sort_values() 將 Series 物件的元素值排序
 to_frame() 將 Series 物件轉成 DataFrame 物件
 transpose() 將 Series 物件轉置後傳回
 unique() 將 Series 物件中獨一無二的元素組成 ndarray 物件傳回



(1). 使用串列建立 Series 物件 :    

將串列傳入 pd.Series 即可建立 Series 物件, 注意, 串列元素資料類型須一致 : 

>>> import pandas as pd    
>>> s=pd.Series([1, 2, 3, 4, 5])          # 傳入串列為值
>>> type(s)    
<class 'pandas.core.series.Series'>      # 型態為 Series 物件
>>> s.values                                    
array([1, 2, 3, 4, 5], dtype=int64)         
>>> type(s.values)    
<class 'numpy.ndarray'>            # Series 物件的內容 (值) 是 Numpy 的 ndarray 物件
>>> s.values.shape                   # Series 物件的內容 (值) 是一維陣列
(5,)   
>>> s.index     
RangeIndex(start=0, stop=5, step=1)       
>>> type(s.index)                     
<class 'pandas.core.indexes.range.RangeIndex'>    # Series 物件的索引是 Index 物件 
>>> print(s)        # 顯示 Series 物件內容
0    1
1    2
2    3
3    4
4    5
dtype: int64   
>>> s.dtype   
dtype('int64')    

顯示 Series 物件時看起來它似乎是二維陣列, 其實它是標示了索引的一維陣列 (行向量), 左邊那欄是它的索引, 預設是 0 起始的連續整數, 可以看成是列的編號或名稱, 右邊那欄則是值. 

可以在建立 Series 物件時傳入 index 參數指定標籤索引, 這樣既可以用預設的整數索引存取, 也可以用標籤索引存取, 例如 :

>>> s=pd.Series([1, 2, 3, 4, 5], index=('a', 'b', 'c', 'd', 'e'))     # 指定標籤索引
>>> print(s)    
a    1
b    2
c    3
d    4
e    5
dtype: int64
>>> s[0]         # 用預設的整數索引存取
1
>>> s['a']       # 用標籤索引存取
1
>>> s.index    
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')    # 索引是字串物件

也可以在建立 Series 物件之後再指定 index 屬性的內容來更改索引, 例如 :

>>> s=pd.Series([1, 2, 3, 4, 5])      # 預設為整數索引
>>> print(s)   
0    1
1    2
2    3
3    4
4    5
dtype: int64
>>> s.index=['a', 'b', 'c', 'd', 'e']     # 設定標籤索引
>>> print(s)   
a    1
b    2
c    3
d    4
e    5
dtype: int64 

可見索引已經變成字串標籤了. 

若傳入的 index 是整數序列, 則預設的 0 起始整數索引將被取代, 不可再使用, 若仍要使用預設的 0 起始整數索引必須改用 iloc 屬性存取, 例如 :

>>> s=pd.Series([1, 2, 3, 4, 5], index=range(1, 6))    
>>> print(s)   
1    1
2    2
3    3
4    4
5    5
dtype: int64
>>> s.index       
RangeIndex(start=1, stop=6, step=1)    # 索引為 1~5 的連續整數

>>> s[0]      # 預設的 0 起始整數索引已被取代, 不可再使用
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\pandas\core\indexes\range.py", line 351, in get_loc
    return self._range.index(new_key)
ValueError: 0 is not in range

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
  File "C:\Python37\lib\site-packages\pandas\core\series.py", line 853, in __getitem__
    return self._get_value(key)
  File "C:\Python37\lib\site-packages\pandas\core\series.py", line 961, in _get_value
    loc = self.index.get_loc(label)
  File "C:\Python37\lib\site-packages\pandas\core\indexes\range.py", line 353, in get_loc
    raise KeyError(key) from err
KeyError: 0   

>>> s.iloc[0]      # 改用 iloc 屬性存取
'蘋果'

可見若不是傳入整數序列的 index (例如字串串列), 則預設的 0 起始整數索引仍可同時使用; 但若傳入整數序列的 index 就不可以使用, 會出現 KeyError 錯誤, 這時必須改用 iloc 屬性存取. 


(2). 使用字典建立 Series 物件 :

建立 Series 物件時若傳入字典, 則 Pandas 會把字典的 key 抽出來做為索引, 把字典的 value 抽出來做為值, 所以其實可以把 Series 看成是有順序的字典 (字典是無序的), 例如 : 

>>> fruits={'apple': '蘋果', 'grape': '葡萄', 'banana': '香蕉'}    
>>> s=pd.Series(fruits)
>>> print(s)   
apple     蘋果
grape     葡萄
banana    香蕉
dtype: object
>>> s.index      
Index(['apple', 'grape', 'banana'], dtype='object')    # 字典的 key 變成索引
>>> s.values    
array(['蘋果', '葡萄', '香蕉'], dtype=object)    # 字典的 value 變成 Series 的值
>>> s[0]        
'蘋果'
>>> s['apple']    
'蘋果'

可見如果字典的 key 是字串, 則除了可以使用這些 key 當索引存取 Series 物件外, 還是可以用預設的 0 起始整數索引去存取. 

但是如果字典的 key 是整數, 則預設的 0 起始連續索引會被抽出的 key 取代, 存取不存在的整數索引位置會出現錯誤, 例如 : 

>>> fruits={1: '蘋果', 2: '葡萄', 3: '香蕉'}      # 字典的 key 是整數
>>> s=pd.Series(fruits)     
>>> s.index    
Int64Index([1, 2, 3], dtype='int64')     # 字典的 key 被抽出來當 Series 的索引
>>> s.values    
array(['蘋果', '葡萄', '香蕉'], dtype=object)    
>>> s[1]        # 用字典的 key 存取 Series 
'蘋果'
>>> s[2]        # 用字典的 key 存取 Series
'葡萄'
>>> s[3]        # 用字典的 key 存取 Series
'香蕉'
>>> s[0]        # 0 不是字典的 key
Traceback (most recent call last):
  File "C:\Python37\lib\site-packages\pandas\core\indexes\base.py", line 3081, in get_loc
    return self._engine.get_loc(casted_key)
  File "pandas\_libs\index.pyx", line 70, in pandas._libs.index.IndexEngine.get_loc
  File "pandas\_libs\index.pyx", line 101, in pandas._libs.index.IndexEngine.get_loc
  File "pandas\_libs\hashtable_class_helper.pxi", line 1625, in pandas._libs.hashtable.Int64HashTable.get_item
  File "pandas\_libs\hashtable_class_helper.pxi", line 1632, in pandas._libs.hashtable.Int64HashTable.get_item
KeyError: 0

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
  File "C:\Python37\lib\site-packages\pandas\core\series.py", line 853, in __getitem__
    return self._get_value(key)
  File "C:\Python37\lib\site-packages\pandas\core\series.py", line 961, in _get_value
    loc = self.index.get_loc(label)
  File "C:\Python37\lib\site-packages\pandas\core\indexes\base.py", line 3083, in get_loc
    raise KeyError(key) from err
KeyError: 0


(3). 使用 Numpy 陣列建立 Series 物件 :

Numpy 的 ndarray 陣列也可以傳入 pd.Series() 來建立 Series 物件, 例如 :

>>> import numpy as np    
>>> s=pd.Series(np.array([1, 2, 3, 4, 5]))      # 傳入 ndarray 物件
>>> s.index     
RangeIndex(start=0, stop=5, step=1)     
>>> s.values     
array([1, 2, 3, 4, 5])    
>>> print(s)   
0    1
1    2
2    3
3    4
4    5
dtype: int32   
>>> s.dtype   
dtype('int32')

可見除了 dtype 是 int32 外, 與直接傳入串列結果基本上是一樣的 (直接傳入串列得到的 dtype 是 int64). 

沒有留言 :