1. 程式人生 > >Pandas資料型別及操作

Pandas資料型別及操作

摘要: pandas 提供了一組標準的時間序列處理工具和資料演算法
資料型別及操作
Python 標準庫的 datetime

datetime 模組中的 datetime、 time、 calendar 等類都可以用來儲存時間型別以及進行一些轉換和運算操作。

lang:python
>>> from datetime import datetime
>>> now = datetime.now()
>>> now
datetime.datetime(2014, 6, 17, 15, 56, 19, 313193)
>>> 
delta = datetime(2010,2,2)-datetime(2010,2,1) >>> delta datetime.timedelta(1) >>> now + delta datetime.datetime(2014, 6, 18, 15, 56, 19, 313193)

datetime 物件間的減法運算會得到一個 timedelta 物件,表示一個時間段。

datetime 物件與它所儲存的字串格式時間戳之間可以互相轉換。str() 函式是可用的,但更推薦 datetime.strptime() 方法。這個方法可以實現雙向轉換。

lang:python
>>> 
str(now) '2014-06-17 15:56:19.313193' >>> now.strftime('%Y-%m-%d') '2014-06-17' >>> datetime.strptime('2010-01-01','%Y-%m-%d') datetime.datetime(2010, 1, 1, 0, 0)

如 %Y 這種格式代表了某種具體的意義,但用著很麻煩。因此可以使用一個名為 dateutil 第三方包的 parser.parse() 函式實現自動轉義,它幾乎可以解析任何格式(這也可能會帶來麻煩)。

lang:python
>>> from
dateutil.parser import parse >>> parse('01-02-2010',dayfirst=True) datetime.datetime(2010, 2, 1, 0, 0) >>> parse('01-02-2010') datetime.datetime(2010, 1, 2, 0, 0) >>> parse('55') datetime.datetime(2055, 6, 17, 0, 0)

pandas 的 TimeStamp

pandas 最基本的時間日期物件是一個從 Series 派生出來的子類 TimeStamp,這個物件與 datetime 物件保有高度相容性,可通過 pd.to_datetime() 函式轉換。(一般是從 datetime 轉換為 Timestamp)

lang:python
>>> pd.to_datetime(now)
Timestamp('2014-06-17 15:56:19.313193', tz=None)
>>> pd.to_datetime(np.nan)
NaT

pandas 的時間序列

pandas 最基本的時間序列型別就是以時間戳(TimeStamp)為 index 元素的 Series 型別。

lang:python
>>> dates = [datetime(2011,1,1),datetime(2011,1,2),datetime(2011,1,3)]
>>> ts = Series(np.random.randn(3),index=dates)
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
dtype: float64
>>> type(ts)
<class 'pandas.core.series.Series'>
>>> ts.index
<class 'pandas.tseries.index.DatetimeIndex'>
[2011-01-01, ..., 2011-01-03]
Length: 3, Freq: None, Timezone: None
>>> ts.index[0]
Timestamp('2011-01-01 00:00:00', tz=None)

時間序列之間的算術運算會自動按時間對齊。

索引、選取、子集構造

時間序列只是 index 比較特殊的 Series ,因此一般的索引操作對時間序列依然有效。其特別之處在於對時間序列索引的操作優化。如使用各種字串進行索引:

lang:python
>>> ts['20110101']
0.36228897878097266
>>> ts['2011-01-01']
0.36228897878097266
>>> ts['01/01/2011']
0.36228897878097266

對於較長的序列,還可以只傳入 “年” 或 “年月” 選取切片:

lang:python
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2012-12-25    0.111869
dtype: float64
>>> ts['2012']
2012-12-25    0.111869
dtype: float64
>>> ts['2011-1-2':'2012-12']
2011-01-02    0.586695
2011-01-03   -0.154522
2012-12-25    0.111869
dtype: float64

除了這種字串切片方式外,還有一種例項方法可用:ts.truncate(after=’2011-01-03’)。

值得注意的是,切片時使用的字串時間戳並不必存在於 index 之中,如 ts.truncate(before=’3055’) 也是合法的。

日期的範圍、頻率以及移動

pandas 中的時間序列一般被預設為不規則的,即沒有固定的頻率。但出於分析的需要,我們可以通過插值的方式將序列轉換為具有固定頻率的格式。一種快捷方式是使用 .resample(rule) 方法:

lang:python
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2011-01-06    0.222958
dtype: float64
>>> ts.resample('D')
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2011-01-04         NaN
2011-01-05         NaN
2011-01-06    0.222958
Freq: D, dtype: float64

生成日期範圍

pd.date_range() 可用於生成指定長度的 DatetimeIndex。引數可以是起始結束日期,或單給一個日期,加一個時間段引數。日期是包含的。

lang:python
>>> pd.date_range('20100101','20100110')
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-01, ..., 2010-01-10]
Length: 10, Freq: D, Timezone: None
>>> pd.date_range(start='20100101',periods=10)
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-01, ..., 2010-01-10]
Length: 10, Freq: D, Timezone: None
>>> pd.date_range(end='20100110',periods=10)
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-01, ..., 2010-01-10]
Length: 10, Freq: D, Timezone: None

預設情況下,date_range 會按天計算時間點。這可以通過 freq 引數進行更改,如 “BM” 代表 bussiness end of month。

lang:python
>>> pd.date_range('20100101','20100601',freq='BM')
<class 'pandas.tseries.index.DatetimeIndex'>
[2010-01-29, ..., 2010-05-31]
Length: 5, Freq: BM, Timezone: None

頻率和日期偏移量

pandas 中的頻率是由一個基礎頻率和一個乘陣列成的。基礎頻率通常以一個字串別名表示,如上例中的 “BM”。對於每個基礎頻率,都有一個被稱為日期偏移量(date offset)的物件與之對應。可以通過例項化日期偏移量來建立某種頻率:

lang:python
>>> Hour()
<Hour>
>>> Hour(2)
<2 * Hours>
>>> Hour(1) + Minute(30)
<90 * Minutes>

但一般來說不必這麼麻煩,使用前面提過的字串別名來建立頻率就可以了:

lang:python
>>> pd.date_range('00:00','12:00',freq='1h20min')
<class 'pandas.tseries.index.DatetimeIndex'>
[2014-06-17 00:00:00, ..., 2014-06-17 12:00:00]
Length: 10, Freq: 80T, Timezone: None

可用的別名,可以通過 help() 或 文件來查詢,這裡就不寫了。

移動(超前和滯後)資料

移動(shifting)指的是沿著時間軸將資料前移或後移。Series 和 DataFrame 都有一個 .shift() 方法用於執行單純的移動操作,index 維持不變:

lang:python
>>> ts
2011-01-01    0.362289
2011-01-02    0.586695
2011-01-03   -0.154522
2011-01-06    0.222958
dtype: float64
>>> ts.shift(2)
2011-01-01         NaN
2011-01-02         NaN
2011-01-03    0.362289
2011-01-06    0.586695
dtype: float64
>>> ts.shift(-2)
2011-01-01   -0.154522
2011-01-02    0.222958
2011-01-03         NaN
2011-01-06         NaN
dtype: float64

上例中因為移動操作產生了 NA 值,另一種移動方法是移動 index,而保持資料不變。這種移動方法需要額外提供一個 freq 引數來指定移動的頻率:

lang:python
>>> ts.shift(2,freq='D')
2011-01-03    0.362289
2011-01-04    0.586695
2011-01-05   -0.154522
2011-01-08    0.222958
dtype: float64
>>> ts.shift(2,freq='3D')
2011-01-07    0.362289
2011-01-08    0.586695
2011-01-09   -0.154522
2011-01-12    0.222958
dtype: float64

時期及其算術運算

本節使用的時期(period)概念不同於前面的時間戳(timestamp),指的是一個時間段。但在使用上並沒有太多不同,pd.Period 類的建構函式仍需要一個時間戳,以及一個 freq 引數。freq 用於指明該 period 的長度,時間戳則說明該 period 在公園時間軸上的位置。

lang:python
>>> p = pd.Period(2010,freq='M')
>>> p
Period('2010-01', 'M')
>>> p + 2
Period('2010-03', 'M')

上例中我給 period 的構造器傳了一個 “年” 單位的時間戳和一個 “Month” 的 freq,pandas 便自動把 2010 解釋為了 2010-01。

period_range 函式可用於建立規則的時間範圍:

lang:python
>>> pd.period_range('2010-01','2010-05',freq='M')
<class 'pandas.tseries.period.PeriodIndex'>
freq: M
[2010-01, ..., 2010-05]
length: 5

PeriodIndex 類儲存了一組 period,它可以在任何 pandas 資料結構中被用作軸索引:

lang:python
>>> Series(np.random.randn(5),index=pd.period_range('201001','201005',freq='M'))
2010-01    0.755961
2010-02   -1.074492
2010-03   -0.379719
2010-04    0.153662
2010-05   -0.291157
Freq: M, dtype: float64

時期的頻率轉換

Period 和 PeriodIndex 物件都可以通過其 .asfreq(freq, method=None, how=None) 方法被轉換成別的頻率。

lang:python
>>> p = pd.Period('2007',freq='A-DEC')
>>> p.asfreq('M',how='start')
Period('2007-01', 'M')
>>> p.asfreq('M',how='end')
Period('2007-12', 'M')
>>> ts = Series(np.random.randn(1),index=[p])
>>> ts
2007   -0.112347
Freq: A-DEC, dtype: float64
>>> ts.asfreq('M',how='start')
2007-01   -0.112347
Freq: M, dtype: float64

時間戳與時期間相互轉換

以時間戳和以時期為 index 的 Series 和 DataFrame 都有一對 .to_period() 和 to_timestamp(how=’start’) 方法用於互相轉換 index 的型別。因為從 period 到 timestamp 的轉換涉及到一個取端值的問題,所以需要一個額外的 how 引數,預設為 ‘start’:

lang:python
>>> ts = Series(np.random.randn(5),index=pd.period_range('201001','201005',freq='M'))
>>> ts
2010-01   -0.312160
2010-02    0.962652
2010-03   -0.959478
2010-04    1.240236
2010-05   -0.916218
Freq: M, dtype: float64
>>> ts.to_timestamp()
2010-01-01   -0.312160
2010-02-01    0.962652
2010-03-01   -0.959478
2010-04-01    1.240236
2010-05-01   -0.916218
Freq: MS, dtype: float64
>>> ts.to_timestamp(how='end')
2010-01-31   -0.312160
2010-02-28    0.962652
2010-03-31   -0.959478
2010-04-30    1.240236
2010-05-31   -0.916218
Freq: M, dtype: float64
>>> ts.to_timestamp().to_period()
2010-01-01 00:00:00.000   -0.312160
2010-02-01 00:00:00.000    0.962652
2010-03-01 00:00:00.000   -0.959478
2010-04-01 00:00:00.000    1.240236
2010-05-01 00:00:00.000   -0.916218
Freq: L, dtype: float64
>>> ts.to_timestamp().to_period('M')
2010-01   -0.312160
2010-02    0.962652
2010-03   -0.959478
2010-04    1.240236
2010-05   -0.916218
Freq: M, dtype: float64

重取樣及頻率轉換

重取樣(resampling)指的是將時間序列從一個頻率轉換到另一個頻率的過程。pandas 物件都含有一個 .resample(freq, how=None, axis=0, fill_method=None, closed=None, label=None, convention=’start’, kind=None, loffset=None, limit=None, base=0) 方法用於實現這個過程。

本篇最前面曾用 resample 規整化過時間序列。當時進行的是插值操作,因為原索引的頻率與給出的 freq 引數相同。resample 方法更多的應用場合是 freq 發生改變的時候,這時操作就分為升取樣(upsampling)和降取樣(downsampling)兩種。具體的區別都體現在引數裡。

lang:python
>>> ts
2010-01   -0.312160
2010-02    0.962652
2010-03   -0.959478
2010-04    1.240236
2010-05   -0.916218
Freq: M, dtype: float64
>>> ts.resample('D',fill_method='ffill')#升取樣
2010-01-01   -0.31216
2010-01-02   -0.31216
2010-01-03   -0.31216
2010-01-04   -0.31216
2010-01-05   -0.31216
2010-01-06   -0.31216
2010-01-07   -0.31216
2010-01-08   -0.31216
2010-01-09   -0.31216
2010-01-10   -0.31216
2010-01-11   -0.31216
2010-01-12   -0.31216
2010-01-13   -0.31216
2010-01-14   -0.31216
2010-01-15   -0.31216
...
2010-05-17   -0.916218
2010-05-18   -0.916218
2010-05-19   -0.916218
2010-05-20   -0.916218
2010-05-21   -0.916218
2010-05-22   -0.916218
2010-05-23   -0.916218
2010-05-24   -0.916218
2010-05-25   -0.916218
2010-05-26   -0.916218
2010-05-27   -0.916218
2010-05-28   -0.916218
2010-05-29   -0.916218
2010-05-30   -0.916218
2010-05-31   -0.916218
Freq: D, Length: 151
>>> ts.resample('A-JAN',how='sum')#降取樣
2010   -0.312160
2011    0.327191
Freq: A-JAN, dtype: float64