pandas to_datetime 的快取
===
###### tags: `Tech` `Python`
> [name=Jade Lin]
> [time= May 2022]
# 前言
要將字串轉換成 `datetime`,相比同功能的函式,`pandas.to_datetime`的效能應該是滿好的。我是參考[這裡](https://ehmatthes.com/blog/faster_than_strptime/)。
如果查 `to_datetime` 效能問題,可能會得到一些回答。但是如果該做的都做了,卻比別人測出來的慢超多,或甚至就像我遇到的案例,明明什麼都沒改,偏偏就是有的處理會特別慢呢?這可能和資料特性有關…
:::spoiler TL;TD :speech_balloon:
> 部門的專案裡,有一個小程式,每次會從資料庫,拉取資料做處理。隨著資料量越來越大,從資料庫拉取資料的時間變得太長,以致於該程式無法在目標時間內消化完資料。於是DBA提供另一個語法,讓我測試看看能提升多少拉取資料的速度。原本的語法必須經過某個排序,而新的語法不經過排序(若排序也有額外的效能問題),但最後取得的資料是一樣的,**只是順序不同**。
>
> 於是我用新的語法,測試從資料庫拉取大量資料,觀察到時間上有大幅度的改善,就很開心的把程式正式更改成這種新語法。
>
> BUT!人生就是有這樣的BUT!程式實際運作後,卻發現程式的總處理時間變得更長了?!我可以很確定我沒有改到其它部分,那……只好進去挖到底是哪裡變慢了……
>
> 在這個小程式處理這些資料的過程中,需要將部份欄位的值做格式轉換。其中有些是日期字串,要轉成`datetime`,尤其面對的資料,不同欄位也會含有不同格式的日期字串。最後選擇使用`to_datetime`,實際運作起來,效能的確也滿不錯。
>
> 但現在**單純只是資料順序改變,在轉換日期的效能變得極差,變差時的執行時間甚至超過十倍** =.=
>
> 實在太奇怪了,於是 trace code 到 pandas 裡,才發現原來`to_datetime`有**是否快取**的差異……
>
:::
# to_datetime 做快取與否
進入本篇的主題:[pandas.to_datetime](https://pandas.pydata.org/docs/reference/api/pandas.to_datetime.html)
如果仔細看官方文件,to_datetime 有一個引數藏在最後面:`cache`
:::info
**cache: bool, default True**
If True, use a cache of unique, converted dates to apply the datetime conversion. May produce significant speed-up when parsing duplicate date strings, especially ones with timezone offsets. The cache is only used when there are at least 50 values. The presence of out-of-bounds values will render the cache unusable and may slow down parsing.
:::
這裡提到,對有重複的日期字串的資料,開啟快取能有很大幅度的加速。另外,在50筆資料以下不會啟用快取。
但這段文字只說到這,單看這個也不會覺得有什麼關係,畢竟`cache`預設值是`True`,那應該表示只要大於50筆資料,就是要有快取吧?呵呵,才不是呢 =.=
仔細看 [pandas datetime source code](https://github.com/pandas-dev/pandas/blob/main/pandas/core/tools/datetimes.py)
會看到引數`cache`會傳入`_maybe_cache`,而這裡的引數`cache`只是初步看要不要試著做快取。所以當`cache == False`就肯定不會做快取,可能用於幾乎沒有重複的資料上。
若直接用預設值`cache == True`呼叫,則決定是否做快取的邏輯,實際上會在`should_cache`函式裡。此時會看到這段文字:
:::info
By default for a sequence of less than 50 items in size, we don't do caching; for the number of elements less than 5000, we take ten percent of all elements to check for a uniqueness share; if the sequence size is more than 5000, then we check only the first 500 elements.
All constants were chosen empirically by.
:::
個人解讀:
* 少於50筆資料,不會做快取。這個和一開始看到的說明一致。
* 若少於5000筆資料,會取10%的資料,檢查資料差異性有多大。差異性太大則不做快取。
* 若大於5000筆資料,則檢查前500筆資料,檢查資料差異性有多大。差異性太大則不做快取。
* 上面這些常數(50、500、10%等等這些數字),是憑經驗決定的。
所以資料內容是會影響是否啟用快取的。那怎麼"**檢查資料差異性有多大**"?
這裡我們以5000筆資料來看:
* 取出前500筆 `check_count==500`
* 取出不重覆的資料清單 `unique_elements`
* 預設的比例`unique_share==0.7`
* 實際公式是
```python
do_caching = len(unique_elements) <= check_count * unique_share
```
也就是說,一般以預設值下去運作的話,超過5000筆資料,它會取前500筆資料,計算**不重複的值的數量**,若**超過** `500*0.7 = 350`的數量,即認定資料差異性過大,它就**不會做快取**;反之,若是<=350 的話,表示資料差異性不大,它就會執行快取的機制,來加速日期的轉換。
至於當資料在50~5000筆之間時,也是取10%的值來判斷要不要做快取,但是畢竟數量不大,有沒有差距可能也不會太明顯。
到這裡就可以理解我遇到的案例,只是資料的順序改變了,卻造成巨大的效能差異的原因。
# 案例實測結果
* Python: 3.9.x
* pandas: 1.3.2
* date str format: `%Y-%m-%dT%H:%M:%S.%f%z`
轉換單欄資料,從字串轉成`datetime`:
| data counts | spent time | with cache |
| ----------- | ---------- | ---------- |
| 13,995 | <font color=blue>0.051s</font> | **YES** |
| 13,995 | <font color=red>0.581s</font> | **NO** |
| 848,990 | <font color=blue>3.626s</font> | **YES** |
| 848,990 | <font color=red>41.954s</font> | **NO** |
可以看出,以這程式面對到的資料特性來說,基於同樣的機器環境、處理同樣的資料,使用快取與否,造成極大的效能差異。
# Conclusion
注意資料順序可能會影響到處理的效能。根據面對到的資料特性,可以注意一下是否有遇到相似的狀況,再視情況看怎麼調整出最好的使用方式。
我遇到的案例,後來是在轉換日期前,先將資料做排序。因為即使排序多花費了幾秒,對於因此省下來的轉換時間,還是遠遠值得啊~~
還有,註解真的是很棒的東西。不然在 trace code 時,應該會花更多的時間在猜作者的想法吧!