整理總結 python 中時間日期類數據處理與類型轉換(含 pandas)


我自學 python 編程並付諸實戰,迄今三個月。 pandas可能是我最高頻使用的庫,基於它的易學、實用,我也非常建議朋友們去嘗試它。——尤其當你本身不是程序員,但多少跟表格或數據打點交道時,pandasexcelVBA 簡單優雅多了。

pandas 善於處理表格類數據,而我日常接觸的數據天然帶有時間日期屬性,比如用戶行為日志、爬蟲爬取到的內容文本等。於是,使用 pandas 也就意味着相當頻繁地與時間日期數據打交道。這篇筆記將從我的實戰經驗出發,整理我常用的時間日期類數據處理、類型轉換的方法。

與此相關的三個庫如下。


import time
import datetime
import pandas as pd

其中,timedatetime都是 python 自帶的,pandas則是一個第三方庫。換言之,前兩者無需額外安裝,第三方庫則需要通過pip install pandas命令行自行安裝。如何檢查自己是否安裝了某個庫,如何安裝它,又如何查看和更新版本,對新手來說是一個比較大的話題,也是基礎技能,值得另外整理一篇筆記,就不在這里占篇幅了。當然,如果你不想自己本地折騰,也可電腦瀏覽器訪問https://xue.cn 這樣的網站,網頁上直接寫代碼並運行它們。

一、time模塊

time模塊,我最常用到的功能就三個:

  • 指定程序休眠;
  • 獲取當前時間戳;
  • 時間戳與本地時間的互相轉換

time.sleep(s) 指定程序休眠 s 秒

指定程序休眠時間,通常是在長時間運行的循環任務中進行。比如爬蟲任務,控制讀取網頁的時間間隔;自循環任務的時間間隔,調用瀏覽器打開網頁的時間間隔等等。

先用兩個打印語句,輔助觀察和理解time.sleep()的效果:

print(datetime.datetime.now())
time.sleep(5)
print(datetime.datetime.now())

至於長時間運行的循環任務,我通常是把核心業務邏輯封裝好,利用jupyter lab自帶的多進程特定,建一個 notebook 放入下面這個函數去持續運行。


def repeat_myself(how_many_times = 10):
    print('--------',how_many_times,'----------')
    # 被封裝的核心代碼
    your_main_def() 

    # 自循環減 1 ;如果剩余次數是0,則終止自循環
    how_many_times += -1
    if how_many_times == 0:
        print(datetime.datetime.now(),'stop it.')
        return

    # 每次調用設定一個時間間隔
    print(datetime.datetime.now(),'have a rest')
    how_long = random.randint(30,120)
    time.sleep(how_long)
    return repeat_myself(how_many_times)

repeat_myself(12)

time.time()獲取當前時間戳

最初我認為無需急於掌握時間戳這個技能點,但實戰中,1) 我的爬蟲有時爬取到時間戳類型的數據,為了易讀,要把它轉換為正常人能看懂的方式;2) 使用 mysql 時我關心存儲所占用的空間以及讀寫效率,並獲知一個時間數據存成 char 不如時間戳更節省空間。好吧,實戰需要,那么趕緊掌握起這個小技能吧。

先了解下如何生成時間戳。通過time.time()得到的時間戳,是一個有着10位整數位 + 6位小數位的浮點數,可根據需要簡單運算轉換為需要的 10、13、16 位整數時間戳。

# 獲取當前時間戳

# 值是 1569642653.1041737 ,float
a = time.time()
# 1569642653,得到 10位時間戳,int
b = int(a)
# 1569642653104,得到 13位時間戳,int
c = int(a * 1000)
# 1569642653104173,得到 16位時間戳,int
d = int(a * 1000000)

接下來,了解一下時間戳和人類易讀的時間之間的轉換。

時間戳與人類易讀的時間互相轉換

如上面所示,時間戳是一個floatint類型的數值,至少有 10 位整數。把時間戳轉換為人類易讀的時間,用到的是localtime(),與其相反的是mktime()能把人類易讀的時間轉換為時間戳。


# 時間戳轉換為人類易讀的時間
# 結果是:time.struct_time(tm_year=2019, tm_mon=9, tm_mday=28, tm_hour=12, tm_min=12, tm_sec=1, tm_wday=5, tm_yday=271, tm_isdst=0)
# 數據類型是 time.struct_time
e = time.localtime(a)
f = time.localtime(b)
g = time.localtime(c//1000)
h = time.localtime(d//1000000)

# 人類易讀的時間轉換為時間戳
# 結果是:1569643921.0,float
i = time.mktime(e)
j = time.mktime(f)
k = time.mktime(g)
l = time.mktime(h)

type()檢查,localtime() 得到的結果,是 time.struct_time 類型,直觀可見這個類型對人類依然不是最友好的。最友好的表達將用到 strftimestrptime 這兩個方法,處理 time.struct_timestring字符串 兩個類型的互換。


# 把 struct_time 轉換為指定格式的字符串
# '2019-09-28 12:12:01 Saturday'
good = time.strftime("%Y-%m-%d %H:%M:%S %A", e)

# 把字符串轉換為 struct_time
# 結果是:time.struct_time(tm_year=2019, tm_mon=9, tm_mday=28, tm_hour=12, tm_min=12, tm_sec=1, tm_wday=5, tm_yday=271, tm_isdst=-1)
nice = time.strptime(good,"%Y-%m-%d %H:%M:%S %A")

在我的筆記中,僅整理總結自己常用的方法,至於我自己從未用到或很少用到的方法,並不羅列其中。如有小伙伴希望系統完整地了解,可直接搜:time site:python.org 或點擊訪問官方文檔 能查看完整說明。

二、datetime 模塊

datetime獲取到的時間數據是非常易讀的,在和人交互時,比 time 更好用一些。我通常把 datetime 用於以下 2 個場景。

場景A:log時間戳,打印信息監控代碼運行情況

新手寫代碼,變相就是寫bug,以我自己來說,使用不熟模塊或寫新業務時,寫代碼和調試修復錯誤,占用時間常常各半。采用 jupter lab的 notebook,讓寫代碼和調試方便許多,但依然需要 print() 打印信息方便監控代碼運行情況。比如下方這個代碼片段:

# 顯示效果:2019-09-28 12:44:36.574576 df_rlt ...
print(datetime.datetime.now(),'df_rlt ...')
for one in df_rlt.values:
    print(datetime.datetime.now(),one,'for circle ...')
    try:
        sql_insert = 'INSERT INTO questions(q_id,q_title,q_description,q_keywords,q_people,q_pageview,time) VALUES( "'\
            + str(quesition_id) + '", "' + str(one[0])+ '", "' + str(one[1]) + '", "' + str(one[2]) + '", "' \
            + str(one[3]) + '", "' + str(one[4]) + '", "' + str(datetime.datetime.now()) + '");' 
        sql_update = 'update topic_monitor SET is_title="1" where question_id = "' + str(quesition_id) + '";'
        cursor.execute(sql_insert)
        cursor.execute(sql_update)
        conn.commit()
        print(datetime.datetime.now(),'sql_insert ...')
    except:
        print(datetime.datetime.now(),'sql_insert error...')
        continue

場景B:文件名時間戳,文件名中增加當前日期

文件名中增加當前日期作為參數,既避免文件相互覆蓋(比如數據每天更新,每天導出一次),也方便直觀地查看文件版本。當然啦,如果處理的是超級頻繁導出的文件,精確到天並不滿足需求,可自行精確到時分秒,或直接用int(time.time())時間戳作為文件名中的參數。

# 效果:'d:/out_put/xuecn_comments_statistics_2019-09-28.xlsx'
comms_file = output_path + 'xuecn_comments_statistics_' + str(datetime.datetime.now())[:10] + '.xlsx'

直接搜:datetime site:python.org 或者點擊訪問 python 官方文檔查看超多方法說明。

與官方文檔對比,我已經用到的知識點真是九牛一毛。不過也沒關系,從需要和興趣出發就好,沒必要硬着頭皮把自己打造成移動字典,很多方法呢都是用多了自然記住了,無需反復死記硬背。

三、pandas 中的時間處理

我寫這篇筆記,本就是奔着精進 pandas 來的,前面花了很大篇幅先整理了timedatetime這些基礎功,現在進入重頭戲,即 pandas 中與時間相關的時間處理。

前面兩個部分舉例,處理的均是單個值,而在處理 pandasdataframe 數據類型時,事情會復雜一點,但不會復雜太多。我在實戰中遇到的情況,總結起來無非兩類:

  • 數據類型的互換
  • 索引與列的互換

需要留意的是,數據類型應該靠程序判斷,而非我們人肉判斷。python pandas 判斷數據類型,常用type()df.info() 這兩個方法。

首先,我們構造一個簡單的數據示例 df

構造這個實例,只是為了方便后面的展開。構造一個 dataframe 的方法有非常多。這里就不展開了。

import random
df = pd.DataFrame({
    'some_data' : [random.randint(100,999) for i in range(1,10)],
    'a_col' : '2019-07-12',
    'b_col' : datetime.datetime.now().date(),
    'c_col' : time.time()},
    index=range(1,10))

然后,我們逐項查看它的數據類型

剛學着用pandas經常會因為想當然地認為某個對象是某個數據類型,從而代碼運行報錯。后來學乖,特別留心數據類型。

某個數據是什么類型,如何查看,某個方法對數據類型有什么要求,如何轉換數據類型,這些都是實戰中特別關心的。

# pandas.core.frame.DataFrame
type(df)
# pandas.core.series.Series
type(df['some_data'])
# numpy.ndarray
type(df['some_data'].values)
# numpy.int64
type(df['some_data'].values[0])
# str
type(df['a_col'].values[0])
# datetime.date
type(df['b_col'].values[0])
# numpy.float64
type(df['c_col'].values[0])

df.info()
"""
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9 entries, 1 to 9
Data columns (total 4 columns):
some_data    9 non-null int64
a_col        9 non-null object
b_col        9 non-null object
c_col        9 non-null float64
dtypes: float64(1), int64(1), object(2)
memory usage: 420.0+ bytes
"""

為什么要轉換數據類型,有什么用途

為什么要把時間日期之類的數據轉換為 pandas 自帶的 datetime64 類型呢?這當然不是強迫症整潔癖,而且即便不做轉換也不會帶來任何報錯。

最重要的原因是,數據分析將會高頻用到基於時間的統計,比如:每天有多少用戶注冊、登錄、付費、留言……產品運營通常按日統計,把dt.date改成dt.weekdt.monthdt.hour就能輸出周統計、月統計、分時統計……當然官方文檔介紹的方法還有更多,我提到的僅是自己高頻使用的方法。


df.groupby(df['c_col'].dt.date).some_data.agg('sum')

次要的原因是,輸出數據到 excel 表格中發給其它同事時,咱們還是得考慮文件的易讀、簡潔吖。比如,時間戳得轉換為人能看懂的文本,比如僅顯示日期,無需把后面時分秒之類的冗余數據也顯示出來等等。

通過不同方式拿到的數據類型,通常相互之間並不一致,而我們想要使用某些方法提高生產力,必須遵循該方法所要求的數據類型。於是數據類型轉換就成了剛需。

如何轉換為 pandas 自帶的 datetime 類型

在上方示例中,肉眼可見 a_colb_col 這兩列都是日期,但 a_col 的值其實是string 字符串類型,b_col的值是datatime.date類型。想要用pandas 的按時間屬性分組的方法,前提是轉換為 pandas 自己的 datetime類型。

轉換方法是一致的:

# 字符串類型轉換為 datetime64[ns] 類型
df['a_col'] = pd.to_datetime(df['a_col'])
# datetime.date 類型轉換為 datetime64[ns] 類型
df['b_col'] = pd.to_datetime(df['b_col'])
# 時間戳(float) 類型轉換為 datetime64[ns] 類型
df['c_col'] = pd.to_datetime(df['c_col'].apply(lambda x:time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(x))))

# 查看轉換后的屬性
df.info()
"""
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9 entries, 1 to 9
Data columns (total 4 columns):
some_data    9 non-null int64
a_col        9 non-null datetime64[ns]
b_col        9 non-null datetime64[ns]
c_col        9 non-null datetime64[ns]
dtypes: datetime64[ns](3), int64(1)
memory usage: 420.0 bytes
"""

其中,難點是 c_col 這列。其實不難,只是幾個嵌套,顯得有點復雜而已:

  1. y = time.localtime(x),把 x 從時間戳(10個整數位+6個小數位的那串數字)類型轉換為struct_time
  2. z = time.strftime('%Y-%m-%d %H:%M:%S',y) 把上一步得到的 struct_time 轉換為 字符串
  3. lambda x:z 匿名函數,輸入一個值x,得到字符串z
  4. df['c_col'].apply() 對整列每個值做上述匿名函數所定義的運算,完成后整列值都是字符串類型
  5. pd.to_datetime() 把整列字符串轉換為 pandas 的 datetime 類型,再重新賦值給該列(相當於更新該列)

我其實非常希望有個過來人告訴我,這個知識點用的頻繁嗎,在什么時期是否應該掌握?於是我自己寫的筆記,通常都會留意分享自己實戰過來的這個判斷。當然啦,每個人實戰的方向不太一樣,大家可作參考,無需完全照搬。具體說來:

  • 第 1、2 步是第一部分 time 模塊總結到基礎技能。
  • 第 3 步的匿名函數 lambda 是相當風騷的知識點,xue.cn 《自學是門手藝》有一節專門講到它,建議掌握。
  • 第 4 步結合匿名函數lambda,是對 dataframe 整列進行統一操作的重要技能點,多用幾次就熟練了。
  • 第 5 步 無需死記硬背。為啥我總說 pandas 易學好用呢?因為它的很多方法,都能直接見文生義,幾乎沒有記憶負擔。

關於時間日期處理的pandas 官方文檔篇幅也挺長的,沒中文版,大家想要系統了解,直接點開查閱吧~

關於索引與列的互換

不管何種原因導致,通常使用 pandas 時會經常對索引與列進行互換。比如把某列時間數據設為索引,把時間索引設為一列……這些操作並沒有額外的特別之處,都統一在pandas 如何進行索引與列的互換 這個技能點之下。限於篇幅,我這里就不展開啦。不過索引與列的轉換是高頻操作,值得另寫一篇筆記。

有一點反復強調都不過為,即,我的筆記僅記錄自己實戰中頻繁遇到的知識技能,並非該模塊全貌。如需系統掌握或遇到筆記之外的疑問,請善用搜索技能喲:你的關鍵詞們 site:python.org

如果我的整理帶給你幫助,請點個贊鼓勵我繼續分享。如需勘誤請留言,或挪步到我的 github 提issues


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM