顯示具有 Pandas 標籤的文章。 顯示所有文章
顯示具有 Pandas 標籤的文章。 顯示所有文章

2023年2月2日 星期四

高科大還書 3 本 (PyTorch, Excel+Python, NLP)

因為尚未開學, 所以今天中午午休時到母校還下列三本被預約的書 : 
No.1 只看了前兩章, 還好市圖也可以借到. No.2 為強國人所寫, 主要是講用 Pandas 取代 Excel, 編排極好, 但我只看了四章, 也沒時間做測試寫筆記, 等我搞定目前學研進度再回借. No.3 借來只看了第一章, 此書是 Oreily 出版的 "Practical Natural Language Processing: A Comprehensive Guide to Building Real-World Nlp Systems" 中譯版, 參考 :


2022年12月6日 星期二

市圖還書 1 本 (用Excel學Python資料分析)

今日去河堤快走後順路去河堤還書 : 

Source : 博客來


此書名雖有 Excel, 其實大部分是在講 Pandas, 範例淺顯易懂, 篇排甚佳, 市圖只進一本, 但借之者眾, 現在預約要一年後才會輪到. 哇哇, 乾脆買一本比較快. 

2022年7月19日 星期二

Python 學習筆記 : 同時追蹤比特幣與乙太幣行情

本篇是前兩篇追蹤兩大虛擬幣 : 比特幣與虛擬幣行情的綜合測試, 參考 :


主要修改部分是爬蟲函式名稱, 因為可通用所以改成 coingecko_crawler(), 其餘只是複製貼上, 完整程式碼如下 : 


測試 1 : 擷取 CoinGecko 網站資料繪製比特幣+乙太幣價格變化圖形 [看原始碼]

import requests
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image

def coingecko_crawler(url):
    res=requests.get(url)
    prices=res.json()['stats']
    df=pd.DataFrame(prices)
    df.columns=['datetime', 'TWD']
    df['datetime']=pd.to_datetime(df['datetime'], unit='ms')
    df['datetime'] += pd.DateOffset(hours=8)
    df.index=df['datetime']
    return df

def notify_image(msg, token, image):
    url='https://notify-api.line.me/api/notify'
    headers={'Authorization': 'Bearer ' + token}
    data={'message': msg}
    image=open(image, 'rb')
    imageFile={'imageFile': image}
    r=requests.post(url, headers=headers, data=data, files=imageFile)
    if r.status_code==requests.codes.ok:
        return '圖片發送成功!'
    else:
        return f'圖片發送失敗: {r.status_code}'

plt.rcParams["font.family"]=["Microsoft JhengHei"]
token='ud7PaDL45fz849A0e1f5oaMCbRIkxMXapQCt7PfNkzz'   
# 比特幣
url='https://www.coingecko.com/price_charts/1/twd/90_days.json'
btc=coingecko_crawler(url)
btc['100MA']=btc['TWD'].rolling(window=100).mean()
btc[['TWD', '100MA']].plot(kind='line')
last_time=str(btc.iloc[-1:].index.values[0])[0:19]
last_price=int(btc.iloc[-1:]["TWD"].values[0])
plt.title(f'比特幣 {last_time} 台幣 {last_price} 元')
plt.xlabel('')
plt.ylabel('價格(台幣)')
plt.grid(True)
plt.legend(['價格', '100小時均價'], loc='best') 
plt.savefig('btc_prices.jpg')
img=Image.open('btc_prices.jpg')       # 開啟檔案   
img=img.crop((18, 26, 590, 453))       # 裁切圖片去除外圍白邊  
img.save('btc_prices.jpg')                    # 回存檔案   
msg=f'\n比特幣 {last_time} 台幣 {last_price} 元'
notify_image(msg, token, 'btc_prices.jpg')

# 乙太幣
url='https://www.coingecko.com/price_charts/279/twd/90_days.json'
eth=coingecko_crawler(url)
eth['100MA']=eth['TWD'].rolling(window=100).mean()
eth[['TWD', '100MA']].plot(kind='line')
last_time=str(eth.iloc[-1:].index.values[0])[0:19]
last_price=int(eth.iloc[-1:]["TWD"].values[0])
plt.title(f'乙太幣 {last_time} 台幣 {last_price} 元')
plt.xlabel('')
plt.ylabel('價格(台幣)')
plt.grid(True)
plt.legend(['價格', '100小時均價'], loc='best') 
plt.savefig('eth_prices.jpg')
img=Image.open('eth_prices.jpg')       # 開啟檔案   
img=img.crop((0, 26, 590, 453))       # 裁切圖片去除外圍白邊  
img.save('eth_prices.jpg')             # 回存檔案   
msg=f'\n乙太幣 {last_time} 台幣 {last_price} 元'
notify_image(msg, token, 'eth_prices.jpg')
#plt.show()  # 測試時才使用

此處最後一列的 plt.show() 是測試時檢視用, 上線時要註解掉. 結果如下 : 




可見兩大虛擬幣走勢幾乎一樣. 

接下來是佈署到樹莓派, 但因為此程式在繪圖時用到微軟正黑體中文字型, 樹莓派沒有此字型, 故須下載字型後進型設定, 方法參考 :


用 WinSCP 將爬蟲程式傳送到樹莓派後用下列指令將其改為可執行 :

pi@raspberrypi:~ $ sudo chmod +x /home/pi/btc_eth_prices_line_notify.py

然後編輯 Crontab, 加入執行此程式的時間指令, 參考 :

 
pi@raspberrypi:~ $ crontab -e   
crontab: installing new crontab
pi@raspberrypi:~ $ crontab -l    
0 16 * * 1-5 /usr/bin/python3 /home/pi/twstock_dashboard_update.py
*/30 9-14 * * 1-5 /usr/bin/python3 /home/pi/yahoo_twstock_monitor.py
0 8,18 * * * /usr/bin/python3 /home/pi/btc_eth_prices_line_notify.py    

OK, 收工啦. 

樹莓派執行 Pandas 出現 "Original error was: libf77blas.so.3"

晚上在佈署比特幣與乙太幣爬蟲程式到樹莓派時, 手動執行卻出現如下錯誤 : 

pi@raspberrypi:~ $ python3 btc_eth_prices_line_notify.py    
Traceback (most recent call last):
  File "btc_eth_prices_line_notify.py", line 2, in <module>
    import pandas as pd
  File "/home/pi/.local/lib/python3.7/site-packages/pandas/__init__.py", line 17, in <module>
    "Unable to import required dependencies:\n" + "\n".join(missing_dependencies)
ImportError: Unable to import required dependencies:
numpy:

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python3.7 from "/usr/bin/python3"
  * The NumPy version is: "1.20.3"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: libf77blas.so.3: cannot open shared object file: No such file or directory    

乍看似乎是在 import pandas 時出現錯誤, 但實際上是載入 Numpy 中的 C 程式時有問題, 因為 Pandas 是以 Numpy 為基礎的. 進入 IDLE 介面匯入 numpy 果然出現與上面相同的錯誤訊息, 匯入 pandas 也是一樣. 

以 "Original error was: libf77blas.so.3" 搜尋, 在下面這篇文章找到解決辦法 :

 
原來只要用下列指令安裝 libatlas-base-dev 即可 : 

sudo apt-get install libatlas-base-dev

pi@raspberrypi:~ $ sudo apt-get install libatlas-base-dev    
正在讀取套件清單... 完成
正在重建相依關係
正在讀取狀態資料... 完成
下列的額外套件將被安裝:
  libatlas3-base
建議套件:
  libatlas-doc liblapack-doc
下列【新】套件將會被安裝:
  libatlas-base-dev libatlas3-base
升級 0 個,新安裝 2 個,移除 0 個,有 0 個未被升級。
需要下載 5,365 kB 的套件檔。
此操作完成之後,會多佔用 32.1 MB 的磁碟空間。
是否繼續進行 [Y/n]? [Y/n] Y
下載:1 http://mirror.ossplanet.net/raspbian/raspbian buster/main armhf libatlas3-base armhf 3.10.3-8+rpi1 [2,399 kB]
下載:2 http://mirror.ossplanet.net/raspbian/raspbian buster/main armhf libatlas-base-dev armhf 3.10.3-8+rpi1 [2,966 kB]
取得 5,365 kB 用了 6s (902 kB/s)
選取了原先未選的套件 libatlas3-base:armhf。
(讀取資料庫 ... 目前共安裝了 168524 個檔案和目錄。)
正在準備解包 .../libatlas3-base_3.10.3-8+rpi1_armhf.deb……
Unpacking libatlas3-base:armhf (3.10.3-8+rpi1) ...
選取了原先未選的套件 libatlas-base-dev:armhf。
正在準備解包 .../libatlas-base-dev_3.10.3-8+rpi1_armhf.deb……
Unpacking libatlas-base-dev:armhf (3.10.3-8+rpi1) ...
設定 libatlas3-base:armhf (3.10.3-8+rpi1) ...
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/libblas.so.3 來提供 /usr/lib/arm-linux-gnueabihf/libblas.so.3 (libblas.so.3-arm-linux-gnueabihf)
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/liblapack.so.3 來提供 /usr/lib/arm-linux-gnueabihf/liblapack.so.3 (liblapack.so.3-arm-linux-gnueabihf)
設定 libatlas-base-dev:armhf (3.10.3-8+rpi1) ...
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/libblas.so 來提供 /usr/lib/arm-linux-gnueabihf/libblas.so (libblas.so-arm-linux-gnueabihf)
update-alternatives: 在自動模式下以 /usr/lib/arm-linux-gnueabihf/atlas/liblapack.so 來提供 /usr/lib/arm-linux-gnueabihf/liblapack.so (liblapack.so-arm-linux-gnueabihf)
執行 libc-bin (2.28-10+rpi1) 的觸發程式……

經過這樣處理後就能順利匯入 Numpy 與 Pamdas 了 :

pi@raspberrypi:~ $ python3    
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np    
>>> np.__version__    
'1.20.3'
>>> import pandas as pd   
>>> pd.__version__    
'1.2.4'

但再次執行爬蟲程式還是失敗, 因為爬蟲程式裡面用到了中文字型 "微軟正黑體", 而樹莓派預設並未支援此字型 : 

pi@raspberrypi:~ $ python3 btc_eth_prices_line_notify.py    
Unable to init server: Could not connect: Connection refused
Unable to init server: 無法連接:Connection refused

(btc_eth_prices_line_notify.py:10639): Gdk-CRITICAL **: 20:43:38.213: gdk_cursor_new_for_display: assertion 'GDK_IS_DISPLAY (display)' failed

(btc_eth_prices_line_notify.py:10639): Gdk-CRITICAL **: 20:43:38.228: gdk_cursor_new_for_display: assertion 'GDK_IS_DISPLAY (display)' failed
/usr/lib/python3/dist-packages/matplotlib/font_manager.py:1241: UserWarning: findfont: Font family ['Microsoft JhengHei'] not found. Falling back to DejaVu Sans.
  (prop.get_family(), self.defaultFamily[fontext]))

但我參考之前的文章要做字型設定時卻發現 matplotlib 似乎版本太舊, 找不到預期之字型資料夾, 所以乾脆用 pip3 install matplotlib -U 指令將 Matplotlib 版本升級到最新, 然後就可以依照之前的文章上傳並設定微軟正黑體 : 


Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import matplotlib  
>>> matplotlib.__version__    
'3.5.2'   

這樣就可以順利在樹莓派用 Matplotlib 繪製含有中文的圖形了.

Python 學習筆記 : 乙太幣行情追蹤

上週利用爬蟲去比特幣網站 CoinGeko 抓比特幣行情, 然後用 Matplotlib 繪製價格趨勢圖以 Line Notify 推播, 參考 : 

 
其實抓第二大虛擬幣乙太幣的價格方法相同, 只是 json 資料源網址不同而已 : 





主要是改 url 部分, 然後將 'btc' 改成 'eth' 就可以報完工了 : 


測試 1 : 擷取 CoinGecko 網站資料繪製乙太幣價格變化圖形 [看原始碼]

import requests
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image

def coingecko_eth_crawler(url):
    res=requests.get(url)
    prices=res.json()['stats']
    df=pd.DataFrame(prices)
    df.columns=['datetime', 'TWD']
    df['datetime']=pd.to_datetime(df['datetime'], unit='ms')
    df['datetime'] += pd.DateOffset(hours=8)
    df.index=df['datetime']
    return df

def notify_image(msg, token, image):
    url='https://notify-api.line.me/api/notify'
    headers={'Authorization': 'Bearer ' + token}
    data={'message': msg}
    image=open(image, 'rb')
    imageFile={'imageFile': image}
    r=requests.post(url, headers=headers, data=data, files=imageFile)
    if r.status_code==requests.codes.ok:
        return '圖片發送成功!'
    else:
        return f'圖片發送失敗: {r.status_code}'

plt.rcParams["font.family"]=["Microsoft JhengHei"]
url='https://www.coingecko.com/price_charts/279/twd/90_days.json'  
eth=coingecko_eth_crawler(url)
eth['100MA']=eth['TWD'].rolling(window=100).mean()
eth[['TWD', '100MA']].plot(kind='line')
last_time=str(eth.iloc[-1:].index.values[0])[0:19]
last_price=int(eth.iloc[-1:]["TWD"].values[0])
plt.title(f'乙太幣 {last_time} 台幣 {last_price} 元')
plt.xlabel('')
plt.ylabel('價格(台幣)')
plt.grid(True)
plt.legend(['價格', '100小時均價'], loc='best') 
plt.savefig('eth_prices.jpg')
img=Image.open('eth_prices.jpg')       # 開啟檔案   
img=img.crop((0, 26, 590, 453))         # 裁切圖片去除外圍白邊  
img.save('eth_prices.jpg')                    # 回存檔案   
token='ud7PaDL45fz849A0e1f5oaMCbRIkxMXapQCt7PfNkzz'
msg='乙太幣價格變化'
notify_image(msg, token, 'eth_prices.jpg')
plt.show()

此例是從比特幣那篇最後的範例修改而來 (中文化+調整時差), 結果如下 : 




從曲線趨勢看 (典型 W 底), 最近比特幣與乙太幣似乎有谷底翻身之勢啊!

2022年7月13日 星期三

Python 學習筆記 : 比特幣行情追蹤

最近股市空頭陰霾壟罩, 幣圈也是掉入熊市, 比特幣等虛擬幣也是跌很慘, 我在雜誌上看到一篇文章, 作者認為市場反轉時機可以看兩個指標, 一是要看通膨有否有好轉跡象; 另外則是看比特幣價格是否回升. 雖然我對虛擬幣還是敬而遠之 (了解不夠), 但透過比特幣價格起伏來觀察它與股市的關係卻有點興趣. 

我在下面這本書找到現成範例, 今天就來測試看看唄! 

# Python 技術者們-實踐!  (旗標, 2018) 第六章

書中利用爬蟲程式到比特幣網站 CoinGeko 擷取近 90 天 (三個月) 的價量資訊, 然後用 Pandas 將其轉成 DataFrame 並計算 100 小時移動平均價, 再用 Matplotlib 繪製價格與均線趨勢圖. 


1. 從比特幣網站 CoinGecko 取得資料來源之 URL :    

首先連到 CoinGecko 網站, 首頁會列出前 100 種虛擬幣 :





其中排第一的就是比特幣, 點比特幣進去會顯示價格趨勢變化圖 : 




按滑鼠右鍵點選 "檢視網頁原始碼", 然後在原始碼頁面按 Ctrl-F 搜尋 "TradingView", 上面這個圖形的資料源網址就在它底下的 a 標籤內 : 




將此頁往右邊拉, 就可以看到 a 標籤的 href 屬性值, 我們的目標是 90d 的 json 檔 : 




將此 90_days.json 檔之網址複製下來給爬蟲使用 : 


點上面超連結可知比特幣過去 90 天每小時的價格資料都被放在 stats 鍵裡面 (成交量則是放在 'total_volumes' 鍵), 其值為以二維陣列表示的時間序列, 第一個元素是 1970/01/01 以來的毫秒時戳, 第二個元素為台幣計價的 BTC 價格 : 




接下來就可以撰寫爬蟲程式來取得這些價格資料. 


2. 以爬蟲程式擷取資料 :

首先匯入 requests 與 pandas : 

>>> import requests    
>>> import pandas as pd      

然後將上面取得的 90d_days.json 檔網址傳入 requests.get() 擷取資料, 它會傳回一個 Response 物件, 呼叫 Response 物件的 json() 方法可將回應內容轉成 Python 的字典, 價格資料會被轉成二維串列, 放在其 stats 鍵的值裡面 (原始 json 資料中還有一個 'total_volumes' 鍵用來存放成交量), 然後傳給 pd.DataFrame() 轉成資料框 : 

>>> url='https://www.coingecko.com/price_charts/1/twd/90_days.json'    
>>> res=requests.get(url)   
>>> type(res)   
<class 'requests.models.Response'>     
>>> res_json=res.json()                  # 將 json 格式資料轉成 Python 字典
>>> type(res_json)       
<class 'dict'>   
>>> prices=res_json['stats']           # 由 stats 鍵取得價格的時間序列資料
>>> type(prices)   
<class 'list'>   
>>> df=pd.DataFrame(prices)      # 將串列轉成資料框
>>> df   
                  0             1
0     1649919786292  1.194869e+06
1     1649923299298  1.198578e+06
2     1649926928481  1.197027e+06
3     1649930479641  1.195884e+06
4     1649934193291  1.192530e+06
...             ...           ...
2151  1657681238819  5.831092e+05
2152  1657684945130  5.827014e+05
2153  1657688435442  5.825076e+05
2154  1657692023661  5.845832e+05
2155  1657694917114  5.841831e+05

[2156 rows x 2 columns]

這裡僅顯示資料框 df 的前五筆與末五筆資料, 可見比特幣在過去三個月內價格已腰斬. 


3. 用 Pandas 清理資料 :

為了後續處理方便還需要進行資料清理作業, 首先是要將欄索引從預設的 0 與 1 改為較易理解的 'datetime' 與 'TWD', 然後呼叫 pd.to_datetime() 將毫秒時戳欄位資料型態改為 datatime, 並以此欄作為列索引, 例如 : 

>>> df.columns=['datetime', 'TWD']      # 設定欄索引
>>> df['datetime']=pd.to_datetime(df['datetime'], unit='ms')     # 轉成 datetime 型態
>>> df.index=df['datetime']       # 設定列索引
>>> df   
                                       datetime           TWD
datetime                                                     
2022-04-14 07:03:06.292 2022-04-14 07:03:06.292  1.194869e+06
2022-04-14 08:01:39.298 2022-04-14 08:01:39.298  1.198578e+06
2022-04-14 09:02:08.481 2022-04-14 09:02:08.481  1.197027e+06
2022-04-14 10:01:19.641 2022-04-14 10:01:19.641  1.195884e+06
2022-04-14 11:03:13.291 2022-04-14 11:03:13.291  1.192530e+06
...                                         ...           ...
2022-07-13 03:00:38.819 2022-07-13 03:00:38.819  5.831092e+05
2022-07-13 04:02:25.130 2022-07-13 04:02:25.130  5.827014e+05
2022-07-13 05:00:35.442 2022-07-13 05:00:35.442  5.825076e+05
2022-07-13 06:00:23.661 2022-07-13 06:00:23.661  5.845832e+05
2022-07-13 06:48:37.114 2022-07-13 06:48:37.114  5.841831e+05

[2156 rows x 2 columns]

可見這些價格資料的時間間隔是以小時為單位, 注意, 這個時間是 UTC 時間, 如果要轉成台灣時間需加 8 小時, 不過為了避免複雜這裡暫時不處理. 

雖然這樣就可以進行價格趨勢的繪圖, 但如果能加上移動平均線會更容易看出趨勢, 移動平均值可以用 Series 物件的 rolling(window).mean() 方法來計算, 其中傳入參數 window 為移動窗的大小, 單位為小時, 以 window=100 (約 4 天) 為例 : 

>>> df['100MA']=df['TWD'].rolling(window=100).mean()   # 計算 100 小時移動平均
>>> df   
                                       datetime           TWD          100MA
datetime                                                                    
2022-04-14 07:03:06.292 2022-04-14 07:03:06.292  1.194869e+06            NaN
2022-04-14 08:01:39.298 2022-04-14 08:01:39.298  1.198578e+06            NaN
2022-04-14 09:02:08.481 2022-04-14 09:02:08.481  1.197027e+06            NaN
2022-04-14 10:01:19.641 2022-04-14 10:01:19.641  1.195884e+06            NaN
2022-04-14 11:03:13.291 2022-04-14 11:03:13.291  1.192530e+06            NaN
...                                         ...           ...            ...
2022-07-13 03:00:38.819 2022-07-13 03:00:38.819  5.831092e+05  618021.586268
2022-07-13 04:02:25.130 2022-07-13 04:02:25.130  5.827014e+05  617369.868976
2022-07-13 05:00:35.442 2022-07-13 05:00:35.442  5.825076e+05  616759.612546
2022-07-13 06:00:23.661 2022-07-13 06:00:23.661  5.845832e+05  616202.822676
2022-07-13 06:48:37.114 2022-07-13 06:48:37.114  5.841831e+05  615632.029145

[2156 rows x 3 columns]

注意, 因為要滿 100 小時才能計算移動平均值, 所以前 100 筆的 '100MA' 欄位為 NaN.


4. 用 Matplotlib 繪製價格趨勢圖 :

接下來就可以用列索引 'datetime' 欄位當 X 軸, 用 'TWD' 與 '100MA' 欄位值當 Y 軸繪製折線圖, 這時需先匯入 Matplotlib (因為 Pandas 是依賴 Matplotlib 繪圖的) :

>>> import matplotlib.pyplot as plt   
>>> df[['TWD', '100MA']].plot(kind='line')       
<matplotlib.axes._subplots.AxesSubplot object at 0x000002AE63545588>
>>> plt.show()    

結果如下 : 




此圖還有改進空間, 首先是預設的 X 軸標籤 datetime 不需要; Y 軸標籤可設為 Prices (TWD); 以及可在標題列顯示最近一筆之日期時間與價格資訊, 取得資料框最後一筆資料可用 iloc[-1:], 它會傳回一個只有一列資料的 DataFrame 物件 :  

>>> df.iloc[-1:]    
                                       datetime            TWD          100MA
datetime                                                                     
2022-07-13 06:48:37.114 2022-07-13 06:48:37.114  584183.095196  615632.029145
>>> type(df.iloc[-1:])      
<class 'pandas.core.frame.DataFrame'>  

所以只要用 index 屬性即可取得列索引 (日期時間), 它會傳回一個 DatetimeIndex 物件, 其 values 屬性值為一個單一元素 (時間字串) 的 ndarray 陣列, 因此用 [0] 索引即可取得此元素, 然後用 str() 將其轉成字串後以切片取前 19 個字元即可得到 '年-月-日T時-分-秒' 的日期時間字串  : 

>>> df.iloc[-1:].index    
DatetimeIndex(['2022-07-13 06:48:37.114000'], dtype='datetime64[ns]', name='datetime', freq=None)
>>> df.iloc[-1:].index.values    
array(['2022-07-13T06:48:37.114000000'], dtype='datetime64[ns]')
>>> df.iloc[-1:].index.values[0]   
numpy.datetime64('2022-07-13T06:48:37.114000000')  
>>> str(df.iloc[-1:].index.values[0])      
'2022-07-13T06:48:37.114000000'
>>> str(df.iloc[-1:].index.values[0])[0:19]    
'2022-07-13T06:48:37'

價格部分則是要先取 ['TWD'] 欄位, 它會傳回一個 Series 物件, 其 values 屬性會傳回單一元素的陣列, 因此用 [0] 即可取得其值, 然後呼叫 int() 來略掉小數部分 :

>>> df.iloc[-1:]["TWD"]    
datetime
2022-07-13 06:48:37.114    584183.095196
Name: TWD, dtype: float64
>>> type(df.iloc[-1:]["TWD"])     
<class 'pandas.core.series.Series'>
>>> df.iloc[-1:]["TWD"].values    
array([584183.09519612])    
>>> df.iloc[-1:]["TWD"].values[0]    
584183.0951961185
>>> int(df.iloc[-1:]["TWD"].values[0])    
584183

總結以上測試, 可以用下列程式碼來設定圖形標題 : 

>>> last_time=str(df.iloc[-1:].index.values[0])[0:19]    
>>> last_time     
'2022-07-13T06:48:37'  
>>> last_price=int(df.iloc[-1:]["TWD"].values[0])      
>>> last_price      
584183   
>>> df[['TWD', '100MA']].plot(kind='line')                    # 繪製 DataFrame 圖形
<matplotlib.axes._subplots.AxesSubplot object at 0x000002AE6388EB70>
>>> plt.title(f'Bit Coin {last_time} NT${last_price}')      # 設定標題
Text(0.5, 1.0, 'Bit Coin 2022-07-13T06:48:37 NT$584183')
>>> plt.xlabel('')                          # 設定 X 軸標籤 (不顯示)
Text(0.5, 0, '')
>>> plt.ylabel('Price(TWD)')     # 設定 Y 軸標籤
Text(0, 0.5, 'Price(TWD)')
>>> plt.grid(True)                       # 顯示隔線
>>> plt.show()   

結果如下 :




OK, 這樣就算是完成價格趨勢繪圖啦! 

完整程式如下 : 

測試 1 : 擷取 CoinGecko 網站資料繪製比特幣價格變化圖形 [看原始碼]

#btc_prices.py
import requests
import pandas as pd
import matplotlib.pyplot as plt

def coingecko_btc_crawler(url):
    res=requests.get(url)
    prices=res.json()['stats']
    df=pd.DataFrame(prices)
    df.columns=['datetime', 'TWD']
    df['datetime']=pd.to_datetime(df['datetime'], unit='ms')
    df.index=df['datetime']
    return df

url='https://www.coingecko.com/price_charts/1/twd/90_days.json'
btc=coingecko_btc_crawler(url)
btc['100MA']=btc['TWD'].rolling(window=100).mean()
btc[['TWD', '100MA']].plot(kind='line')
last_time=str(btc.iloc[-1:].index.values[0])[0:19]
last_price=int(btc.iloc[-1:]["TWD"].values[0])
plt.title(f'Bit Coin {last_time} NT${last_price}')
plt.xlabel('')
plt.ylabel('Price(TWD)')
plt.grid(True)
plt.savefig('btc_prices.jpg')
plt.show()

此處是將爬蟲部分寫成一個函式. 


5. 用 Line Notify 推播價格趨勢圖 :

上面所繪製的比特幣價格曲線圖片可用 Line Notify 傳送, 方法參考 : 


上面所繪製的曲線圖其實外圍有白邊, 這除了增加圖檔大小外, 在 Line 上也會看起來較小, 可以用 Pillow 套件的 Image 模組將外圍的白邊切除. 先用小畫家開啟 btc_prices.jpg, 確定要裁切的左上角與右下角座標 :




然後將此座標傳入 Image.crop() 進行裁切後再存回 btc_prices.jpg 即可, 例如 : 

>>> from PIL import Image
>>> img=Image.open('btc_prices.jpg')   
>>> img=img.crop((18, 26, 590, 453))      
>>> img.save('btc_prices.jpg')   

參考 :


完整程式碼如下 :
 

測試 2 : 用 Line Notify 推播比特幣價格趨勢圖 [看原始碼]

import requests
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image

def coingecko_btc_crawler(url):
    res=requests.get(url)
    prices=res.json()['stats']
    df=pd.DataFrame(prices)
    df.columns=['datetime', 'TWD']
    df['datetime']=pd.to_datetime(df['datetime'], unit='ms')
    df.index=df['datetime']
    return df

def notify_image(msg, token, image):
    url='https://notify-api.line.me/api/notify'
    headers={'Authorization': 'Bearer ' + token}
    data={'message': msg}
    image=open(image, 'rb')
    imageFile={'imageFile': image}
    r=requests.post(url, headers=headers, data=data, files=imageFile)
    if r.status_code==requests.codes.ok:
        return '圖片發送成功!'
    else:
        return f'圖片發送失敗: {r.status_code}'

url='https://www.coingecko.com/price_charts/1/twd/90_days.json'
btc=coingecko_btc_crawler(url)
btc['100MA']=btc['TWD'].rolling(window=100).mean()
btc[['TWD', '100MA']].plot(kind='line')
last_time=str(btc.iloc[-1:].index.values[0])[0:19]
last_price=int(btc.iloc[-1:]["TWD"].values[0])
plt.title(f'Bit Coin {last_time} NT${last_price}')
plt.xlabel('')
plt.ylabel('Price(TWD)')
plt.grid(True)
plt.savefig('btc_prices.jpg')
img=Image.open('btc_prices.jpg')       # 開啟檔案   
img=img.crop((18, 26, 590, 453))       # 裁切圖片去除外圍白邊  
img.save('btc_prices.jpg')                    # 回存檔案   
token='ud7PaDL45fz849A0e1f5oaMCbRIkxMXapQCt7PfNkzz'
msg='Bit Coin'
notify_image(msg, token, 'btc_prices.jpg')
plt.show()

結果如下 : 




若將上面裁切圖片的三列程式碼去掉, 推播原始圖片的結果如下 :




雖然看起來有較小, 但其實差別不大, 因為點擊圖片瀏覽原圖時幾乎沒差. 

2022-07-19 補充 :

今天抽空解決了上面的時差問題, CoinGeko 網站上的時間是 UTC, 如果要轉成台灣時間需加上 8 小時, 作法是呼叫 pd.DataOffset() 並傳入 hours=8 參數更改 datatime 欄位值即可 : 

df['datetime'] += pd.DateOffset(hours=8)

參考 : 


另外也把 Matplotlib 繪圖中文化, 完整程式碼如下 : 


測試 3 : 用 Line Notify 推播比特幣價格趨勢圖 (台灣時間+中文化) [看原始碼]

import requests
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image

def coingecko_btc_crawler(url):
    res=requests.get(url)
    prices=res.json()['stats']
    df=pd.DataFrame(prices)
    df.columns=['datetime', 'TWD']
    df['datetime']=pd.to_datetime(df['datetime'], unit='ms')
    df['datetime'] += pd.DateOffset(hours=8)
    df.index=df['datetime']
    return df

def notify_image(msg, token, image):
    url='https://notify-api.line.me/api/notify'
    headers={'Authorization': 'Bearer ' + token}
    data={'message': msg}
    image=open(image, 'rb')
    imageFile={'imageFile': image}
    r=requests.post(url, headers=headers, data=data, files=imageFile)
    if r.status_code==requests.codes.ok:
        return '圖片發送成功!'
    else:
        return f'圖片發送失敗: {r.status_code}'

plt.rcParams["font.family"]=["Microsoft JhengHei"]
url='https://www.coingecko.com/price_charts/1/twd/90_days.json'
btc=coingecko_btc_crawler(url)
btc['100MA']=btc['TWD'].rolling(window=100).mean()
btc[['TWD', '100MA']].plot(kind='line')
last_time=str(btc.iloc[-1:].index.values[0])[0:19]
last_price=int(btc.iloc[-1:]["TWD"].values[0])
plt.title(f'比特幣 {last_time} 台幣 {last_price} 元')
plt.xlabel('')
plt.ylabel('價格(台幣)')
plt.grid(True)
plt.legend(['價格', '100小時均價'], loc='best') 
plt.savefig('btc_prices.jpg')
img=Image.open('btc_prices.jpg')       # 開啟檔案   
img=img.crop((18, 26, 590, 453))       # 裁切圖片去除外圍白邊  
img.save('btc_prices.jpg')                    # 回存檔案   
token='ud7PaDL45fz849A0e1f5oaMCbRIkxMXapQCt7PfNkzz'
msg='比特幣價格變化'
notify_image(msg, token, 'btc_prices.jpg')
plt.show()

結果如下 : 




2022年2月22日 星期二

2022年2月9日 星期三

天瓏買書 : 一行指令學 Python-用 Pandas 掌握商務大數據分析

此書我在市圖預約了很久, 超過半年以上才借到, 沒看多少轉眼就要還了, 下一次再回到手上大約是五個月後, 奇怪, 一本 2019 年出版的書怎會排隊預約的人超過 8 個? 原因是作者 (中華大學徐聖訓教授) 很會編書, 他把 Pandas 的重點濃縮在篇幅不大的一本書裡, 我只看了近兩章就覺得很有料 (有許多眉角小提示), 值得再排隊回借, 

但因我最近就會回到 Pandas 的學習佇列, 要我再等五個月會很痛苦, 那就買下來唄 (錢能解決的都是小事)! 但我問了明儀, 他們說全華的書訂量少, 要跟其他的書一起訂, 到貨時間可能會到兩周後. 查了各網路書店, 只有專業的天瓏打九折, 且只要滿 350 即免運費 (PChome 要滿 499), 所以就註冊了帳號下標了 :



Source : 天瓏


九折後是 468 元免運費. 

2021年10月12日 星期二

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

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


本系列測試參考書目如下 :
  1. 人工智慧 Python 基礎課 (碁峰, 陳會安)
  2. Python 金融分析 第二版 (碁峰, 賴屹民譯)
  3. Pandas 資料分析實戰 第二版 (博碩, 陳建宏譯)
  4. Python 資料科學學習手冊 (碁峰, 何敏煌譯)
  5. 用 Excel 學 Python 資料分析 (碁峰, 張俊紅)
  6. Python for Excel (歐萊里, 沈佩誼譯)
  7. Python for Data Analysis 3rd (Oreilly, 2022)
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' 

用 dir(pd) 會列出所有 Pandas 的成員, 裡面包含名稱有底線的保留成員, 以下改用一個自訂模組 members 的 list_members() 函式來列出模組或套件中的公開成員 (包含屬性與方法), 參考 :
>>> import pandas as pd
>>> import members   
>>> members.list_members(pd)      
BooleanDtype <class 'type'>
Categorical <class 'abc.ABCMeta'>
CategoricalDtype <class 'type'>
CategoricalIndex <class 'type'>
DataFrame <class 'type'>
DateOffset <class 'pandas._libs.tslibs.offsets.OffsetMeta'>
DatetimeIndex <class 'type'>
DatetimeTZDtype <class 'type'>
ExcelFile <class 'type'>
ExcelWriter <class 'abc.ABCMeta'>
Flags <class 'type'>
Float32Dtype <class 'type'>
Float64Dtype <class 'type'>
Float64Index <class 'type'>
Grouper <class 'type'>
HDFStore <class 'type'>
Index <class 'type'>
IndexSlice <class 'pandas.core.indexing._IndexSlice'>
Int16Dtype <class 'type'>
Int32Dtype <class 'type'>
Int64Dtype <class 'type'>
Int64Index <class 'type'>
Int8Dtype <class 'type'>
Interval <class 'type'>
IntervalDtype <class 'type'>
IntervalIndex <class 'type'>
MultiIndex <class 'type'>
NA <class 'pandas._libs.missing.NAType'>
NaT <class 'pandas._libs.tslibs.nattype.NaTType'>
NamedAgg <class 'type'>
Period <class 'type'>
PeriodDtype <class 'type'>
PeriodIndex <class 'type'>
RangeIndex <class 'type'>
Series <class 'type'>
SparseDtype <class 'type'>
StringDtype <class 'type'>
Timedelta <class 'type'>
TimedeltaIndex <class 'type'>
Timestamp <class 'type'>
UInt16Dtype <class 'type'>
UInt32Dtype <class 'type'>
UInt64Dtype <class 'type'>
UInt64Index <class 'type'>
UInt8Dtype <class 'type'>
api <class 'module'>
array <class 'function'>
arrays <class 'module'>
bdate_range <class 'function'>
compat <class 'module'>
concat <class 'function'>
core <class 'module'>
crosstab <class 'function'>
cut <class 'function'>
date_range <class 'function'>
describe_option <class 'pandas._config.config.CallableDynamicDoc'>
errors <class 'module'>
eval <class 'function'>
factorize <class 'function'>
get_dummies <class 'function'>
get_option <class 'pandas._config.config.CallableDynamicDoc'>
infer_freq <class 'function'>
interval_range <class 'function'>
io <class 'module'>
isna <class 'function'>
isnull <class 'function'>
json_normalize <class 'function'>
lreshape <class 'function'>
melt <class 'function'>
merge <class 'function'>
merge_asof <class 'function'>
merge_ordered <class 'function'>
notna <class 'function'>
notnull <class 'function'>
offsets <class 'module'>
option_context <class 'type'>
options <class 'pandas._config.config.DictWrapper'>
pandas <class 'module'>
period_range <class 'function'>
pivot <class 'function'>
pivot_table <class 'function'>
plotting <class 'module'>
qcut <class 'function'>
read_clipboard <class 'function'>
read_csv <class 'function'>
read_excel <class 'function'>
read_feather <class 'function'>
read_fwf <class 'function'>
read_gbq <class 'function'>
read_hdf <class 'function'>
read_html <class 'function'>
read_json <class 'function'>
read_orc <class 'function'>
read_parquet <class 'function'>
read_pickle <class 'function'>
read_sas <class 'function'>
read_spss <class 'function'>
read_sql <class 'function'>
read_sql_query <class 'function'>
read_sql_table <class 'function'>
read_stata <class 'function'>
read_table <class 'function'>
reset_option <class 'pandas._config.config.CallableDynamicDoc'>
set_eng_float_format <class 'function'>
set_option <class 'pandas._config.config.CallableDynamicDoc'>
show_versions <class 'function'>
test <class 'function'>
testing <class 'module'>
timedelta_range <class 'function'>
to_datetime <class 'function'>
to_numeric <class 'function'>
to_pickle <class 'function'>
to_timedelta <class 'function'>
tseries <class 'module'>
unique <class 'function'>
util <class 'module'>
value_counts <class 'function'>
wide_to_long <class 'function'>

可見此方式的優點是可以知道該成員是類別, 函式, 或套件下的模組. 類別中最重要的是 Series 與 DataFrame 這兩個, 呼叫其建構函式 pd.Series() 與 pd.DataFrame() 即可建立 Pandas 的核心物件 Series 與 DataFrame 物件.

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




由於 DataFrame 的階層式索引功能也可以用來表示三維陣列, 因此 Panel 的應用並不多, 所以  DataFrame 成為 Pandas 最常用的資料結構, 而 Series 物件可以視為組成 DataFrame 的組件, 事實上, 將 DataFrame 的各欄拆開就是一個個長度相同的 Series 物件. 


2. 建立 Series 物件 :   

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

建立 Series 物件的語法如下 : 

pd.Series(data [, index])      

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

建立 Series 物件後可以用 dir() 來檢視其公開成員, 如果使用上面的自訂模組 members 來檢視會出現 AbstractMethod 例外, 故改為直接檢視 pd.Series 類別 : 

>>> import pandas as pd
>>> import members   
>>> members.list_members(pd.Series)    
T <class 'property'>
abs <class 'function'>
add <class 'function'>
add_prefix <class 'function'>
add_suffix <class 'function'>
agg <class 'function'>
aggregate <class 'function'>
align <class 'function'>
all <class 'function'>
any <class 'function'>
append <class 'function'>
apply <class 'function'>
argmax <class 'function'>
argmin <class 'function'>
argsort <class 'function'>
array <class 'property'>
asfreq <class 'function'>
asof <class 'function'>
astype <class 'function'>
at <class 'property'>
at_time <class 'function'>
attrs <class 'property'>
autocorr <class 'function'>
axes <class 'property'>
backfill <class 'function'>
between <class 'function'>
between_time <class 'function'>
bfill <class 'function'>
bool <class 'function'>
cat <class 'type'>
clip <class 'function'>
combine <class 'function'>
combine_first <class 'function'>
compare <class 'function'>
convert_dtypes <class 'function'>
copy <class 'function'>
corr <class 'function'>
count <class 'function'>
cov <class 'function'>
cummax <class 'function'>
cummin <class 'function'>
cumprod <class 'function'>
cumsum <class 'function'>
describe <class 'function'>
diff <class 'function'>
div <class 'function'>
divide <class 'function'>
divmod <class 'function'>
dot <class 'function'>
drop <class 'function'>
drop_duplicates <class 'function'>
droplevel <class 'function'>
dropna <class 'function'>
dt <class 'type'>
dtype <class 'property'>
dtypes <class 'property'>
duplicated <class 'function'>
empty <class 'property'>
eq <class 'function'>
equals <class 'function'>
ewm <class 'function'>
expanding <class 'function'>
explode <class 'function'>
factorize <class 'function'>
ffill <class 'function'>
fillna <class 'function'>
filter <class 'function'>
first <class 'function'>
first_valid_index <class 'function'>
flags <class 'property'>
floordiv <class 'function'>
ge <class 'function'>
get <class 'function'>
groupby <class 'function'>
gt <class 'function'>
hasnans <class 'property'>
head <class 'function'>
hist <class 'function'>
iat <class 'property'>
idxmax <class 'function'>
idxmin <class 'function'>
iloc <class 'property'>
index <class 'pandas._libs.properties.AxisProperty'>
infer_objects <class 'function'>
interpolate <class 'function'>
is_monotonic <class 'property'>
is_monotonic_decreasing <class 'property'>
is_monotonic_increasing <class 'property'>
is_unique <class 'property'>
isin <class 'function'>
isna <class 'function'>
isnull <class 'function'>
item <class 'function'>
items <class 'function'>
iteritems <class 'function'>
keys <class 'function'>
kurt <class 'function'>
kurtosis <class 'function'>
last <class 'function'>
last_valid_index <class 'function'>
le <class 'function'>
loc <class 'property'>
lt <class 'function'>
mad <class 'function'>
map <class 'function'>
mask <class 'function'>
max <class 'function'>
mean <class 'function'>
median <class 'function'>
memory_usage <class 'function'>
min <class 'function'>
mod <class 'function'>
mode <class 'function'>
mul <class 'function'>
multiply <class 'function'>
name <class 'property'>
nbytes <class 'property'>
ndim <class 'property'>
ne <class 'function'>
nlargest <class 'function'>
notna <class 'function'>
notnull <class 'function'>
nsmallest <class 'function'>
nunique <class 'function'>
pad <class 'function'>
pct_change <class 'function'>
pipe <class 'function'>
plot <class 'type'>
pop <class 'function'>
pow <class 'function'>
prod <class 'function'>
product <class 'function'>
quantile <class 'function'>
radd <class 'function'>
rank <class 'function'>
ravel <class 'function'>
rdiv <class 'function'>
rdivmod <class 'function'>
reindex <class 'function'>
reindex_like <class 'function'>
rename <class 'function'>
rename_axis <class 'function'>
reorder_levels <class 'function'>
repeat <class 'function'>
replace <class 'function'>
resample <class 'function'>
reset_index <class 'function'>
rfloordiv <class 'function'>
rmod <class 'function'>
rmul <class 'function'>
rolling <class 'function'>
round <class 'function'>
rpow <class 'function'>
rsub <class 'function'>
rtruediv <class 'function'>
sample <class 'function'>
searchsorted <class 'function'>
sem <class 'function'>
set_axis <class 'function'>
set_flags <class 'function'>
shape <class 'property'>
shift <class 'function'>
size <class 'property'>
skew <class 'function'>
slice_shift <class 'function'>
sort_index <class 'function'>
sort_values <class 'function'>
sparse <class 'type'>
squeeze <class 'function'>
std <class 'function'>
str <class 'type'>
sub <class 'function'>
subtract <class 'function'>
sum <class 'function'>
swapaxes <class 'function'>
swaplevel <class 'function'>
tail <class 'function'>
take <class 'function'>
to_clipboard <class 'function'>
to_csv <class 'function'>
to_dict <class 'function'>
to_excel <class 'function'>
to_frame <class 'function'>
to_hdf <class 'function'>
to_json <class 'function'>
to_latex <class 'function'>
to_list <class 'function'>
to_markdown <class 'function'>
to_numpy <class 'function'>
to_period <class 'function'>
to_pickle <class 'function'>
to_sql <class 'function'>
to_string <class 'function'>
to_timestamp <class 'function'>
to_xarray <class 'function'>
tolist <class 'function'>
transform <class 'function'>
transpose <class 'function'>
truediv <class 'function'>
truncate <class 'function'>
tshift <class 'function'>
tz_convert <class 'function'>
tz_localize <class 'function'>
unique <class 'function'>
unstack <class 'function'>
update <class 'function'>
value_counts <class 'function'>
values <class 'property'>
var <class 'function'>
view <class 'function'>
where <class 'function'>
xs <class 'function'>

可見 Series 物件有非常多成員, 大部分為方法. 

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'>    # 預設索引是 RangeIndex 物件 
>>> print(s)        # 顯示 Series 物件內容
0    1
1    2
2    3
3    4
4    5
dtype: int64   
>>> s.dtype   
dtype('int64')    

顯示 Series 物件時看起來它似乎是二維陣列, 但其實它只是擁有索引標籤的一維陣列 (行向量) 而已, 雖然 Series 物件由左右兩個一維陣列組成, 左邊的一維陣列只是它的索引, 預設是 0 起始的連續整數, 右邊的陣列才是 Series 的內容. 

可以在建立 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')    # 索引是字串物件

可見將設定字串索引標籤後, index 屬性值變成 object 型態, 而不是預設的 RangeIndex 物件了. 

也可以在建立 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). 


(4). 使用純量建立 Series 物件 :

如果在呼叫 pd.Series() 時傳入一個純量給 data 參數, 則該純量會被重複地填入每一個索引中, 例如 :

>>> import pandas as pd   
>>> s=pd.Series(5, index=('a', 'b', 'c', 'd', 'e'))     
>>> s   
a    5
b    5
c    5
d    5
e    5
dtype: int64

如果要將內容都初始化為同一值, 只要傳入一個純量即可. 


3. 建立 Series 物件 :