- 本篇博文來自《Python數據分析從入門到精通》_明日科技編著
4.8 日期數據處理
4.8.1 DataFrame的日期數據轉換
- 日常工作中,有一個非常麻煩的事情就是日期的格式可以有很多中表達,我們看到同樣的2020年2月14日,可以有很多種格式,如圖4.57所示。那麼,我們需要先將這些格式統一後才能進行後續的工作。Pandas提供了to_datetime()方法可以幫助我們解決這一問題。
- to_datetime()方法可以用來批量處理日期數據轉換,對於處理大數據非常實用和方便,它可以將日期數據轉換成你需要的各種格式。例如,將2/14/20和14-2-2020轉換成日期格式2020-02-14。to_datetime()方法的語法如下:
pandas.to_datetime(arg, errors =’ignore’, dayfirst = False, yearfirst = False, utc = None, box = True, format = None,exact= True, unit = None, infer_datetime_format = False, origin =’unix ‘, cache = False)
- arg:字符串、日期時間、字符串數組
- errors:值為ignore、raise或coerce,具體說明如下,默認值為ignore,即忽略錯誤。
– ignore:無效的解析將返回原值
– raise:無效的解析將引發異常
– coerce:無效的解析將被設置為NaT,即無法轉換為日期的數據將轉換為NaT - dayfirst:第一個為天,布爾型,默認值為False。例如02/09/2020,如果值為True,則解析日期的第一個為天,即2020-09-02;如果值為False,則解析日期與原日期一致,即2020-02-09
- yearfirst:第一個為年,布爾型,默認值為False。例如14-Feb-20,如果值為True,則解析日期的第一個為年,即2014-02-20;如果值為False,則解析日期與原日期一致,即2020-02-14
- utc:布爾型,默認值為None。返回utc即協調世界時間
- box:布爾值,默認值為True,如果值為True,則返回DatatimeIndex;如果值為False,則返回ndarray。
- format:格式化顯示時間的格式。字符串,默認值為None
- exact:布爾值,默認值為True,如果為True,則要求格式完全匹配;如果為False,則允許格式與目標字符串中的任何位置匹配
- unit:默認值為 None,參數的單位(D、s、ms、us、ns)表示時間的單位
- infer_datetime_format:默認值為False。如果沒有格式,則嘗試根據第一個日期時間字符串推斷格式。
- origin:默認值為unix。定義參考日期。數值將被解析為單位數
- cache:默認值為False。如果值為True,則使用唯一、轉換日期的緩存應用日期的時間轉換。在解析重復日期字符串,特別是帶有時區偏移的字符串時,可能會產生明顯的加速。只有在至少有50個值時才使用緩存。越界值的存在將使緩存不可用,並可能減慢解析速度。
- 返回值:日期時間
將各種日期字符串轉換為指定的日期格式
- 將2020年2月14日的各種格式轉換為日期格式,程序代碼如下:
import pandas as pd
df=pd.DataFrame({
'原日期':['14-Feb-20', '02/14/2020', '2020.02.14', '2020/02/14','20200214']})
df['轉換後的日期']=pd.to_datetime(df['原日期'])
print(df)
- 還可以實現從DataFrame對象中的多列,如年、月、日各列組合成一列日期。鍵值時常用的日期縮略語。組合要求:
- 必選:year、month、day
- 可選:hour、minute、second、milisecond(毫秒)、microsecond(微秒)、nanosecond(納秒)。
將一組數據轉換為日期數據
import pandas as pd
df = pd.DataFrame({
'year': [2018, 2019,2020],
'month': [1, 3,2],
'day': [4, 5,14],
'hour':[13,8,2],
'minute':[23,12,14],
'second':[2,4,0]})
df['組合後的日期']=pd.to_datetime(df)
print(df)
4.8.2 dt對象的使用
- dt對象是Series對象中用於獲取日期屬性的一個訪問器對象,通過它可以獲取日期中的年、月、日、星期數、季節等,還可以判斷日期是否處在年底。語法如下:
Series.dt()
- 返回值:返回與原始系列相同的索引系列。如果Series不包含日期值,則引發錯誤。
- dt對象提供了year、month、day、dayofweek、dayofyear、is_leap_year、quarter、weekday_name等屬性和方法。例如,year可以獲取"年"、quarter可以直接得到每個日期分別是第幾個季度,weekday_name可以直接得到每個日期對應的是周幾。
獲取日期中的年、月、日、星期數等
import pandas as pd
df=pd.DataFrame({
'原日期':['2019.1.05', '2019.2.15', '2019.3.25','2019.6.25','2019.9.15','2019.12.31']})
df['日期']=pd.to_datetime(df['原日期'])
df
df['年'],df['月'],df['日']=df['日期'].dt.year,df['日期'].dt.month,df['日期'].dt.day
df['星期幾']=df['日期'].dt.day_name()
df['季度']=df['日期'].dt.quarter
df['是否年底']=df['日期'].dt.is_year_end
df
4.8.3 獲取日期區間的數據
df1['2018']
df1['2017':'2018']
df1['2018-07']
df1['2018-05-06':'2018-05-06']
獲取指定日期區間的訂單數據
- 獲取2018年5月11日至6月10日的訂單,結果如下圖所示
import pandas as pd
df = pd.read_excel('mingribooks.xls')
df1=df[['訂單付款時間','買家會員名','聯系手機','買家實際支付金額']]
df1=df1.sort_values(by=['訂單付款時間'])
df1
df1 = df1.set_index('訂單付款時間') # 將日期設置為索引
df1
#獲取某個區間數據
df2=df1['2018-05-11':'2018-06-10']
df2
4.8.4 按不同時期統計並顯示數據
1.按時期統計數據
- 按時期統計數據主要通過DataFrame對象的resample()方法結合數據計算函數實現。resample()方法主要應用於時間序列頻率轉換和重采樣,它可以從日期中獲取年、月、日、星期、季節等,結合數據計算函數就可以實現年、月、日、星期或季度等不同時期統計數據。舉例如下所示。索引必須為日期型。
- (1)按年統計數據,代碼如下:
df1=df1.resample('AS').sum()
df2.resample('Q').sum()
df1.resample('M').sum()
df1.resample('W').sum()
df1.resample('D').sum()
2.按時期顯示數據
- DataFrame對象的to_period()方法可以將時間戳轉換為時期,從而實現按日期顯示數據,前提是日期必須設置為索引。語法如下:
DataFrame.to_period(freq=None,axis=0,copy=True)
- freq:字符串,周期索引的頻率,默認值為None
- axis:行列索引,axis=0表示行索引,axis=1表示列索引。默認值為0,即表示行索引。
- copy:是否復制數據,默認值為True,如果值為False,則不復制數據。
- 返回值:帶周期索引的時間序列
從日期中獲取不同的時期
import pandas as pd
aa =r'TB2018.xls'
df = pd.DataFrame(pd.read_excel(aa))
df1=df[['訂單付款時間','買家會員名','聯系手機','買家實際支付金額']]
df1 = df1.set_index('訂單付款時間') # 將date設置為index
df1
按月統計數據
#按月統計數據
#“MS”是每個月第一天為開始日期,“M”是每個月最後一天
df1.resample('M').sum()
按季統計數據
#按季統計數據
#“QS”是每個季度第一天為開始日期,“Q”是每個季度最後一天
df1.resample('QS').sum()
按年統計數據
#按年統計數據
#“AS”是每年第一天為開始日期,“A”是每年最後一天
df1.resample('AS').sum()
按年統計數據並顯示數據
#按年統計數據並顯示數據
#“AS”是每年第一天為開始日期,“A”是每年最後一天
df1.resample('AS').sum().to_period('A')
按季度統計並顯示數據
# 按季度統計並顯示數據
df1.resample('Q').sum().to_period('Q')
按月統計數據並顯示數據
#按月統計數據
#“MS”是每個月第一天為開始日期,“M”是每個月最後一天
df1.resample('M').sum().to_period('M')
按星期統計並顯示數據
df1.resample('w').sum().to_period('W').head()
4.9 時間序列
4.9.1 重采樣(Resample()方法)
- 通過前面的學習,我們學會了如何生成不同頻率的時間索引,按小時、按天、按周、按月等,如果想對數據做不同頻率的轉換,該怎麼辦?在Pandas中對時間序列的頻率的調整稱為重新采樣,即將時間序列從一個頻率轉換到另一個頻率的處理過程。例如,每天一個頻率轉換為每5天一個頻率,如圖4.67所示。
- 重采樣主要使用resample()方法,該方法用於對常規時間序列重新采樣和頻率轉換,包括降采樣和升采樣兩種。首先了解下resample()方法,語法如下:
DataFrame.resample(rule,how=None,axis=0,fill_method=None,closed=None,label=None,convention='start',kind=None,loffset=None,limit=None,base=0,level=None)
- rule:字符串,偏移量表示目標字符串或對象轉換
- how:用於產生聚合值的函數名或數組函數。例如mean、ohlc和np.max等,默認值為mean,其他常用的值為first、last、median、max和min。
- axis:整型,表示行列,axis=0表示列,axis=1表示行。默認值為0,即表示列
- fill_method:升采樣時所使用的填充方法,fill()方法(用前值填充)或bfill()方法(用後值填充),默認值為None
- closed:降采樣時,時間區間的開和閉,與數學裡區間的概念一樣,其值為right或left,right表示左開右閉,left表示左閉右開,默認值為right左開右閉
- label:降采樣時,如何設置聚合值的標簽。例如,10:30-10:35會被標記成10:30還是10:35,默認值為None
- convention:當重采樣時,將低頻率轉換到高頻率所采用的約定,其值為start或end,默認值為start
- kind:聚合到時期(period)或時間戳(timestamp),默認聚合到時間序列的索引類型,默認值為None。
- loffset:聚合標簽的時間校正值,默認值為None。例如,-1s或second(-1)用於將聚合標簽調早1秒
- limit:向前或向後填充,允許填充的最大時期數,默認值為None
- base:整型,默認值為0。對於均勻細分1天的頻率,聚合間隔的"原點"。例如,對於5min頻率,base的范圍可以是0~4
- on:字符串,可選參數,默認值為None。對DataFrame對象使用列代替索引進行重新采樣。列必須與日期時間類似
- level:字符串,可選參數,默認值為None。用於多索引,重新采樣的級別或級別編號,級別必須與日期時間類似
- 返回值:重新采樣對象
一分鐘的時間序列轉化為3分鐘的時間序列
- 首先創建一個包含9個一分鐘的時間序列,然後使用resample()方法轉換為3分鐘的時間序列,並對索引進行求和計算,如圖4.68所示。
- 程序代碼如下:
import pandas as pd
index = pd.date_range('02/02/2020', periods=9, freq='T')
series = pd.Series(range(9), index=index)
series
series.resample('3T').sum()
4.9.2 降采樣處理
- 降采樣是周期由高頻率轉向低頻率。例如,將5min股票交易數據轉換為日交易,按天統計的銷售數據轉換按周統計。
- 數據降采樣會涉及數據的聚合。例如,天數據變成周數據,那麼就要對1周7天的數據進行聚合,聚合的方式主要包括求和、求均值等。
按周統計銷售數據
import pandas as pd
df=pd.read_excel('time.xls')
df1 = df.set_index('訂單付款時間') #設置“訂單付款時間”為索引
df1
df1.resample('W').sum().head()
#%%
df1.resample('W',closed='left').sum()
4.9.3 升采樣處理
- 升采樣是周期由低頻率轉向高頻率。將數據從低頻率轉換到高頻率時,就不需要聚合了,將其重采樣到日頻率,默認會引入缺失值。
- 例如,原來是按周統計的數據,現在變成按天統計。升采樣會涉及數據的填充,根據填充的方法不同,填充的數據也不同。下面介紹三種填充方法。
– 不填充。空值用NaN代替,使用asfreq()方法。
– 用前值填充。用前面的值填充空值,使用ffill()方法或者pad()方法。為了方便記憶,ffill()方法可以使用它的第一個字母"f"代替,代表forward,向前的意思。
– 用後值填充,使用bfill()方法,可以使用字母"b"代替,代表back,向後的意思。
每6小時統計一次數據
- 下面創建一個時間序列,起始日期是2020-02-02,一共兩天,每天對應的數值分別是1和2,通過升采樣處理為每6小時統計一次數據,空值以不同的方式填充,程序代碼如下:
import pandas as pd
import numpy as np
rng = pd.date_range('20200202', periods=2)
s1 = pd.Series(np.arange(1,3), index=rng)
s1
s1_6h_asfreq = s1.resample('6H').asfreq()
print(s1_6h_asfreq)
s1_6h_pad = s1.resample('6H').pad()
print(s1_6h_pad)
s1_6h_ffill = s1.resample('6H').ffill()
print(s1_6h_ffill)
s1_6h_bfill = s1.resample('6H').bfill()
print(s1_6h_bfill)
4.9.4 時間序列數據匯總(ohlc()函數)
- 在金融領域,經常會看到開盤(open)、收盤(close)、最高價(high)和最低價(low)數據,而在Pandas中經過重新采樣的數據也可以實現這樣的結果,通過調用ohlc()函數得到數據匯總結果,即開始值(open)、結束值(close)、最高值(high)和最低值(low)。ohlc()函數的語法如下:
resample.ohlc()
- ohlc()函數返回DataFrame對象,每組數據的open(開)、high(高)、low(低)和close(關)值。
統計數據的open、high、low和close值。
- 下面每一組5分鐘的時間序列,通過ohlc()函數獲取該時間序列中每組時間的開始值、最高值、最低值和結束值,程序代碼如下:
import pandas as pd
import numpy as np
rng = pd.date_range('2/2/2020',periods=12,freq='T')
s1 = pd.Series(np.arange(12),index=rng)
s1
s1.resample('5min').ohlc()
4.9.5 移動窗口數據計算(rolling()函數)
- 通過重采樣可以得到想要的任何低頻率的數據,但是這些數據也是一共時點的數據,那麼就存在這樣一個問題:時點的數據波動較大,某一點的數據就不能很好地表現它本身的特性,於是就有了"移動窗口"的概念,簡單地說,為了提升數據的可靠性,將某個點的取值擴大到這個點的一段區間,用區間來進行判斷,這個區間就是窗口。
- 下面舉例說明,圖4.74顯示了移動窗口數據示意圖,其中時間序列代表1號到15號每天的銷量數據,接下來以3天為一個窗口,將該窗口從左至右依次移動,統計出3天的平均值作為這個點的值,如3號的銷量是1號、2號和3號的平均值。
- 通過上述示意圖相信您已經理解了移動窗口,在Pandas中可以通過rolling()函數實現移動窗口數據的計算,語法如下:
DataFrame.rolling(window,min_periods=None,center=False,win_type=None,on=None,axis=0,closed=None)
- window:時間窗口的大小,有兩種形式,即int或offset。如果使用int,則數值表示計算統計量的觀測值的數量,即向前幾個數據;如果使用offset,則表示時間窗口的大小。
- min_periods:每個窗口最少包含的觀測值數量,小於這個值的窗口結果為NA。值可以是int,默認值為None。offset情況下,默認值為1
- center:把窗口的標簽設置為劇中。布爾型,默認值為False,居右
- win_type:窗口的類型。截取窗的各種函數。字符串類型,默認值為None
- on:可選參數。對於DataFrame對象,是指定要計算移動窗口的列,值為列名
- axis:整型,axis=0表示列,axis=1表示行。默認值為0,即對列進行計算
- closed:定義區間的開閉,支持int類型的窗口。對於offset類型默認是左開右閉。可以根據情況指定left。
- 返回值:為特定操作而生成的窗口或移動窗口子類
創建淘寶每日銷售數據
import pandas as pd
index=pd.date_range('20200201','20200215')
data=[3,6,7,4,2,1,3,8,9,10,12,15,13,22,14]
s1_data=pd.Series(data,index=index)
s1_data
使用rolling()函數計算3天的均值
- 下面使用rolling()函數計算2020-02-01至2020-02-15中3天的均值,窗口個數為3,代碼如下:
s1_data.rolling(3).mean()
- 運行程序,看下rolling()函數是如何計算的?當窗口開始移動時,第一個時間點2020-02-01和第二個時間點2020-02-02的數值為空,這是因為窗口個數為3,它們前面有空數據,所以均值為空;而到第三個時間點2020-02-03時,它前面的數據是2020-02-01至2020-02-03,所以3天的均值是5033333;以此類推。
用當天的數據代表窗口數據
- 在計算第一個時間點2020-02-01的窗口數據時,雖然數據不夠窗口長度3,但是至少有當天的數據,那麼能否用當天的數據代表窗口數據呢?答案是肯定的,通過設置min_periods參數即可,它表示窗口最少包含的觀測值,小於這個值的窗口長度顯示為空,等於或大於時都有值,主要代碼如下:
s1_data.rolling(3,min_periods=1).mean()
import numpy as np
import pandas as pd
index=pd.date_range('20200201','20200215')
data=[3,6,7,4,2,1,3,8,9,10,12,15,13,22,14]
np.random.seed(2)
data=np.random.randint(20,size=len(index))
ser_data=pd.Series(data,index=index)
plt.figure(figsize=(15, 5))
ser_data.plot(style='r--')
ser_data.rolling(3).mean().plot(style='b')
4.10 綜合應用
案例1:Excel多表合並
- 在日常工作中,幾乎我們每天都有大量的數據需要處理,桌面上總是布滿密密麻麻的Excel表,這樣看上去非常凌亂,其實我們完全可以其中的類別相同的Excel表合並到一起,這樣不但不會丟失數據,而且還可以非常有效地分析數據。下面使用concat()方法指定文件夾內的所有Excel表合並,程序代碼如下:
import pandas as pd
import glob
filearray=[]
filelocation=glob.glob(r'./aa/*.xlsx') #指定目錄下的所有Excel文件 #遍歷指定目錄 for filename in filelocation: filearray.append(filename) print(filename) res=pd.read_excel(filearray[0]) #讀取第一個Excel文件 #順序讀取Excel文件並進行合並 for i in range(1,len(filearray)): A=pd.read_excel(filearray[i]) res=pd.concat([res,A],ignore_index=True,sort=False) #忽略索引,讀取文件詳見pandas統計分析中 print(res.index) #寫入Excel文件,並保存 writer = pd.ExcelWriter('all.xlsx') res.to_excel(writer,'sheet1') writer.save()
4.10.2 案列2:股票行情數據分析
- 股票數據包括開盤價、收盤價、最低價、成交量等多個指標。其中,收盤價是當日行情的標准,也是下一個交易日開盤價的依據,可以預測未來證券市場行情,因此當投資者對行情分析時,一般采用收盤價作為計價依據。
- 下面使用rolling()函數計算某股票20天、50天和200天的收盤價均值並生成走勢圖(也稱K線圖),程序代碼如下:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
aa =r'000001.xlsx'
df = pd.DataFrame(pd.read_excel(aa))
df['date'] = pd.to_datetime(df['date']) #將數據類型轉換為日期類型
df
df = df.set_index('date') # 將date設置為index
df=df[['close']] #提取收盤價數據
df
df['20天'] = np.round(df['close'].rolling(window = 20, center = False).mean(), 2)
df['50天'] = np.round(df['close'].rolling(window = 50, center = False).mean(), 2)
df['200天'] = np.round(df['close'].rolling(window = 200, center = False).mean(), 2)
plt.rcParams['font.sans-serif']=['SimHei'] #解決中文亂碼
df.plot(secondary_y = ["收盤價", "20","50","200"], grid = True)
plt.legend(('收盤價','20天', '50天', '200天'), loc='upper right')