Skip to content

Latest commit

 

History

History
967 lines (723 loc) · 33.6 KB

File metadata and controls

967 lines (723 loc) · 33.6 KB

六、处理缺失数据,时间序列和 Matplotlib 绘图

在本章中,我们将介绍一些必要的主题,这些主题对于培养使用 Pandas 的专业知识必不可少。 这些主题的知识对于准备数据作为处理数据以进行分析,预测或可视化的程序或代码的输入非常有用。 我们将讨论的主题如下:

  • 处理缺失的数据
  • 处理时间序列和日期
  • 使用matplotlib绘图

到本章结束时,用户应该精通这些关键领域。

处理缺失的数据

数据丢失是指由于某种原因在我们的数据集中显示为 NULL 或 N/A 的数据点; 例如,我们可能有一个时间序列,横跨一个月的所有日历日,显示每天股票的收盘价,而非营业日的收盘价则显示为缺失。 一个损坏的数据示例是财务数据集,该数据集以错误的格式显示了交易的活动日期。 例如,由于数据提供者发生错误,因此使用YYYY-MM-DD而不是YYYYMMDD

对于 Pandas,缺失值通常由 NaN 值表示。

除了本机出现在源数据集中之外,还可以通过诸如重新索引或在时间序列的情况下更改频率之类的操作将缺失值添加到数据集中:

In [84]: import numpy as np
 import pandas as pd
 import matplotlib.pyplot as plt
 %matplotlib inline
In [85]: date_stngs = ['2014-05-01','2014-05-02',
 '2014-05-05','2014-05-06','2014-05-07']
 tradeDates = pd.to_datetime(pd.Series(date_stngs))
In [86]: closingPrices=[531.35,527.93,527.81,515.14,509.96]
In [87]: googClosingPrices=pd.DataFrame(data=closingPrices,
 columns=['closingPrice'],
 index=tradeDates)
 googClosingPrices
Out[87]:                closingPrice
 tradeDates 
 2014-05-01       531.35
 2014-05-02       527.93
 2014-05-05       527.81
 2014-05-06       515.14
 2014-05-07       509.96
 5 rows 1 columns

可以在这里找到上述数据的来源。

Pandas 还提供了一个 API,可以从各种数据提供商(例如 Yahoo)读取股票数据:

In [29]: import pandas.io.data as web
In [32]: import datetime
 googPrices = web.get_data_yahoo("GOOG",
 start=datetime.datetime(2014, 5, 1),
 end=datetime.datetime(2014, 5, 7))
In [38]: googFinalPrices=pd.DataFrame(googPrices['Close'],
 index=tradeDates)
In [39]: googFinalPrices
Out[39]:           Close
 2014-05-01  531.34998
 2014-05-02  527.92999
 2014-05-05  527.81000
 2014-05-06  515.14001
 2014-05-07  509.95999

有关更多详细信息,请参见这里

现在,我们有了一个时间序列,描述了 Google 股票自 2014 年 5 月 1 日至 2014 年 5 月 7 日的收盘价,由于交易只在工作日发生,因此日期范围存在缺口。 如果要更改日期范围以使其显示日历日(即周末),则可以将时间序列索引的频率从工作日更改为日历日,如下所示:

In [90]: googClosingPricesCDays=googClosingPrices.asfreq('D')
 googClosingPricesCDays
Out[90]:    closingPrice
 2014-05-01  531.35
 2014-05-02  527.93
 2014-05-03  NaN
 2014-05-04  NaN
 2014-05-05  527.81
 2014-05-06  515.14
 2014-05-07  509.96
 7 rows 1 columns

请注意,我们现在为 2014 年 5 月 3 日和 2014 年 5 月 4 日的周末日期引入了closingPriceNaN值。

我们可以通过使用isnullnotnull函数来检查缺少的值,如下所示:

In [17]: googClosingPricesCDays.isnull()
Out[17]: closingPrice
 2014-05-01   False
 2014-05-02   False
 2014-05-03   True
 2014-05-04   True
 2014-05-05   False
 2014-05-06   False
 2014-05-07   False
 7 rows 1 columns

In [18]: googClosingPricesCDays.notnull()
Out[18]: closingPrice
 2014-05-01   True
 2014-05-02   True
 2014-05-03   False
 2014-05-04   False
 2014-05-05   True
 2014-05-06   True
 2014-05-07   True
 7 rows 1 columns

在每种情况下都会返回一个布尔型数据帧。 在datetime和 Pandas 时间戳中,缺失值由NaT值表示。 对于基于时间的类型,这相当于 Pandas 中的NaN

In [27]: tDates=tradeDates.copy()
 tDates[1]=np.NaN
 tDates[4]=np.NaN

In [28]: tDates
Out[28]: 0   2014-05-01
 1          NaT
 2   2014-05-05
 3   2014-05-06
 4          NaT
 Name: tradeDates, dtype: datetime64[ns]

In [4]: FBVolume=[82.34,54.11,45.99,55.86,78.5]
 TWTRVolume=[15.74,12.71,10.39,134.62,68.84]

In [5]: socialTradingVolume=pd.concat([pd.Series(FBVolume),
 pd.Series(TWTRVolume),
 tradeDates], axis=1,
 keys=['FB','TWTR','TradeDate'])
 socialTradingVolume
Out[5]:      FB       TWTR    TradeDate
 0   82.34    15.74   2014-05-01
 1   54.11    12.71   2014-05-02
 2   45.99    10.39   2014-05-05
 3   55.86    134.62  2014-05-06
 4   78.50    68.84   2014-05-07
 5 rows × 3 columns

In [6]: socialTradingVolTS=socialTradingVolume.set_index('TradeDate')
socialTradingVolTS
Out[6]:
 TradeDate    FB      TWTR
 2014-05-01   82.34   15.74
 2014-05-02   54.11   12.71
 2014-05-05   45.99   10.39
 2014-05-06   55.86   134.62
 2014-05-07   78.50   68.84
 5 rows × 2 columns

In [7]: socialTradingVolTSCal=socialTradingVolTS.asfreq('D')
 socialTradingVolTSCal
Out[7]:
 FB      TWTR
 2014-05-01  82.34   15.74
 2014-05-02  54.11   12.71
 2014-05-03  NaN     NaN
 2014-05-04  NaN     NaN
 2014-05-05  45.99   10.39
 2014-05-06  55.86   134.62
 2014-05-07  78.50   68.84
 7 rows × 2 columns

我们可以对包含缺失值的数据执行算术运算。 例如,我们可以计算 Facebook 和 Twitter 的两只股票的总交易量(百万股),如下所示:

In [8]: socialTradingVolTSCal['FB']+socialTradingVolTSCal['TWTR']
Out[8]: 2014-05-01     98.08
 2014-05-02     66.82
 2014-05-03       NaN
 2014-05-04       NaN
 2014-05-05     56.38
 2014-05-06    190.48
 2014-05-07    147.34
 Freq: D, dtype: float64

默认情况下,对包含缺失值的对象执行的任何操作都会在该位置返回缺失值,如以下命令所示:

In [12]: pd.Series([1.0,np.NaN,5.9,6])+pd.Series([3,5,2,5.6])
Out[12]: 0     4.0
 1     NaN
 2     7.9
 3    11.6
 dtype: float64
In [13]: pd.Series([1.0,25.0,5.5,6])/pd.Series([3,np.NaN,2,5.6])
Out[13]: 0    0.333333
 1         NaN
 2    2.750000
 3    1.071429
 dtype: float64

但是,NumPy 处理聚合计算的方式与 Pandas 的处理方式有所不同。

在 Pandas 中,默认值是将缺失值视为0并进行聚合计算,而对于 NumPy,如果缺少任何值,则返回NaN。 这是一个例子:

In [15]: np.mean([1.0,np.NaN,5.9,6])
Out[15]: nan

In [16]: np.sum([1.0,np.NaN,5.9,6])
Out[16]: nan

但是,如果此数据在 pandas 序列中,则将获得以下输出:

In [17]: pd.Series([1.0,np.NaN,5.9,6]).sum()
Out[17]: 12.9
In [18]: pd.Series([1.0,np.NaN,5.9,6]).mean()
Out[18]: 4.2999999999999998

重要的是要意识到 Pandas 和 NumPy 在行为上的差异。 但是,如果我们希望 NumPy 的行为与 Pandas 相同,则可以使用np.nanmeannp.nansum函数,如下所示:

In [41]: np.nanmean([1.0,np.NaN,5.9,6])
Out[41]: 4.2999999999999998

In [43]: np.nansum([1.0,np.NaN,5.9,6])
Out[43]: 12.9

有关 NumPy np.nan聚合函数的更多信息,请参考这里

处理缺失值

有多种处理缺失值的方法,如下所示:

  1. 通过使用fillna()函数来填充 NA 值。 这是一个例子:

    In [19]: socialTradingVolTSCal
    Out[19]:    FB   TWTR
     2014-05-01  82.34   15.74
     2014-05-02  54.11   12.71
     2014-05-03  NaN     NaN
     2014-05-04  NaN     NaN
     2014-05-05  45.99   10.39
     2014-05-06  55.86   134.62
     2014-05-07  78.50   68.84
     7 rows × 2 columns
    
    In [20]: socialTradingVolTSCal.fillna(100)
    Out[20]:            FB      TWTR
     2014-05-01   82.34   15.74
     2014-05-02   54.11   12.71
     2014-05-03   100.00  100.00
     2014-05-04   100.00  100.00
     2014-05-05   45.99   10.39
     2014-05-06   55.86   134.62
     2014-05-07   78.50   68.84
     7 rows × 2 columns

    我们还可以使用ffillbfill参数填充前向或后向值:

    In [23]: socialTradingVolTSCal.fillna(method='ffill')
    Out[23]:            FB      TWTR
     2014-05-01   82.34   15.74
     2014-05-02   54.11   12.71
     2014-05-03   54.11   12.71
     2014-05-04   54.11   12.71
     2014-05-05   45.99   10.39
     2014-05-06   55.86   134.62
     2014-05-07   78.50   68.84
     7 rows × 2 columns
    
    In [24]: socialTradingVolTSCal.fillna(method='bfill')
    Out[24]:            FB      TWTR
     2014-05-01   82.34   15.74
     2014-05-02   54.11   12.71
     2014-05-03   45.99   10.39
     2014-05-04   45.99   10.39
     2014-05-05   45.99   10.39
     2014-05-06   55.86   134.62
     2014-05-07   78.50   68.84
     7 rows × 2 columns

    pad方法是ffill的替代名称。 有关更多详细信息,您可以转到这里

  2. 通过使用dropna()函数删除/删除缺少值的行和列。 以下是一个示例:

    In [21]: socialTradingVolTSCal.dropna()
    Out[21]:      FB      TWTR
     2014-05-01  82.34   15.74
     2014-05-02  54.11   12.71
     2014-05-05  45.99   10.39
     2014-05-06  55.86   134.62
     2014-05-07  78.50   68.84
     5 rows × 2 columns
  3. 我们还可以使用interpolate()函数对缺失值进行插值和填充,如以下命令所述:

    In [27]: pd.set_option('display.precision',4)
     socialTradingVolTSCal.interpolate()
    Out[27]:       FB       TWTR
     2014-05-01   82.340   15.740
     2014-05-02   54.110   12.710
     2014-05-03   51.403   11.937
     2014-05-04   48.697   11.163
     2014-05-05   45.990   10.390
     2014-05-06   55.860   134.620
     2014-05-07   78.500   68.840
     7 rows × 2 columns

    interpolate()函数还采用一个参数 -- 表示该方法的method。 这些方法包括线性,二次,三次样条等等。 您可以从官方文档中获取更多信息。

处理时间序列

在本节中,我们向您展示如何处理时间序列数据。 我们将首先展示如何使用从csv文件中读取的数据创建时间序列数据。

读取时间序列数据

在这里,我们演示了读取时间序列数据的各种方法:

In [7]: ibmData=pd.read_csv('ibm-common-stock-closing-prices-1959_1960.csv')
 ibmData.head()
Out[7]:    TradeDate     closingPrice
 0   1959-06-29   445
 1   1959-06-30   448
 2   1959-07-01   450
 3   1959-07-02   447
 4   1959-07-06   451
 5 rows 2 columns

可以在这个链接中找到此信息的来源。

我们希望TradeDate列是datetime值的序列,以便我们可以为其编制索引并创建时间序列。 让我们首先检查TradeDate序列中值的类型:

In [16]: type(ibmData['TradeDate'])
Out[16]: pandas.core.series.Series
In [12]: type(ibmData['TradeDate'][0])
Out[12]: str

接下来,我们将其转换为Timestamp类型:

In [17]: ibmData['TradeDate']=pd.to_datetime(ibmData['TradeDate'])
 type(ibmData['TradeDate'][0])
Out[17]: pandas.tslib.Timestamp

现在,我们可以将TradeDate列用作索引:

In [113]: #Convert DataFrame to TimeSeries
 #Resampling creates NaN rows for weekend dates, hence use dropna
 ibmTS=ibmData.set_index('TradeDate').resample('D')['closingPrice'].dropna()
 ibmTS
Out[113]: TradeDate
 1959-06-29    445
 1959-06-30    448
 1959-07-01    450
 1959-07-02    447
 1959-07-06    451
 ...
 Name: closingPrice, Length: 255

日期偏移和时间增量对象

DateOffset对象表示时间的变化或偏移。 DateOffset对象的关键特征如下:

  • 可以将其添加到datetime对象或从中减去,以获得转换后的日期
  • 可以乘以一个整数(正数或负数),以便可以多次应用该增量
  • 它具有前滚和后滚方法,可将日期向前移动到下一个偏移日期或向后移动到上一个偏移日期

我们说明了如何使用日期偏移对象,如下所示:

In [371]: xmasDay=pd.datetime(2014,12,25)
 xmasDay
Out[371]: datetime.datetime(2014, 12, 25, 0, 0)

In [373]: boxingDay=xmasDay+pd.DateOffset(days=1)
 boxingDay
Out[373]: Timestamp('2014-12-26 00:00:00', tz=None)

In [390}: today=pd.datetime.now()
 today
Out[390]: datetime.datetime(2014, 5, 31, 13, 7, 36, 440060)

注意,datetime.datetimepd.Timestamp不同。 前者是 Python 类,效率低下,而后者基于numpy.datetime64数据类型。 pd.DateOffset对象与pd.Timestamp一起使用,并将其添加到datetime.datetime函数中可将该对象转换为pd.Timestamp对象。

下面说明了从今天开始一周的命令:

In [392]: today+pd.DateOffset(weeks=1)
Out[392]: Timestamp('2014-06-07 13:07:36.440060', tz=None)

下图说明了从现在起五年内的命令:

In [394]: today+2*pd.DateOffset(years=2, months=6)
Out[394]: Timestamp('2019-05-30 13:07:36.440060', tz=None)

这是使用rollforward函数的示例。 QuarterBegin是一个DateOffset对象,用于将给定的datetime对象增加到下一个日历季度的开始:

In [18]: lastDay=pd.datetime(2013,12,31)
In [24]: from pandas.tseries.offsets import QuarterBegin
 dtoffset=QuarterBegin()
 lastDay+dtoffset
Out[24]: Timestamp('2014-03-01 00:00:00', tz=None)

In [25]: dtoffset.rollforward(lastDay)
Out[25]: Timestamp('2014-03-01 00:00:00', tz=None)

因此,我们可以看到,2013 年 12 月 31 日之后的下一个季度从 2014 年 3 月 1 日开始。TimedeltasDateOffsets相似,但可用于datetime.datetime对象。 以下命令解释了它们的使用:

In [40]: weekDelta=datetime.timedelta(weeks=1)
 weekDelta
Out[40]: datetime.timedelta(7)

In [39]: today=pd.datetime.now()
 today
Out[39]: datetime.datetime (2014, 6, 2, 3, 56, 0, 600309)

In [41]: today+weekDelta
Out[41]: datetime.datetime (2014, 6, 9, 3, 56,0, 600309)

与时间序列相关的实例方法

在本节中,我们探索用于时间序列对象的各种方法,例如移位,频率转换和重采样。

平移/滞后

有时,我们可能希望将时间序列中的值在时间上向后或向前移动。 一种可能的情况是,数据集包含公司中去年新雇员的开始日期列表,并且公司的人力资源计划希望将这些日期提前一年,以便可以激活雇员的福利。 我们可以通过使用shift()函数来做到这一点,如下所示:

In [117]: ibmTS.shift(3)
Out[117]: TradeDate
 1959-06-29    NaN
 1959-06-30    NaN
 1959-07-01    NaN
 1959-07-02    445
 1959-07-06    448
 1959-07-07    450
 1959-07-08    447
 ...

这将转换所有日历日。 但是,如果我们只希望转移工作日,则必须使用以下命令:

In [119]: ibmTS.shift(3, freq=pd.datetools.bday)
Out[119]: TradeDate
 1959-07-02    445
 1959-07-03    448
 1959-07-06    450
 1959-07-07    447
 1959-07-09    451

在前面的代码片段中,我们指定了freq参数进行平移; 这告诉函数仅更改工作日。 shift函数具有freq参数,其值可以是DateOffset类,类似于timedelta的对象或偏移别名。 因此,使用ibmTS.shift(3, freq='B')也将产生相同的结果。

更改频率

我们可以使用asfreq函数来更改频率,如下所述:

In [131]: # Frequency conversion using asfreq
 ibmTS.asfreq('BM')
Out[131]: 1959-06-30    448
 1959-07-31    428
 1959-08-31    425
 1959-09-30    411
 1959-10-30    411
 1959-11-30    428
 1959-12-31    439
 1960-01-29    418
 1960-02-29    419
 1960-03-31    445
 1960-04-29    453
 1960-05-31    504
 1960-06-30    522
 Freq: BM, Name: closingPrice, dtype: float64

在这种情况下,我们仅从ibmTS时间序列中获取与该月的最后一天相对应的值。 在此,bm代表营业月结束频率。 有关所有可能的频率别名的列表,请访问这里

如果我们指定的频率小于数据的粒度,则间隙将用NaN值填充:

In [132]: ibmTS.asfreq('H')
Out[132]: 1959-06-29 00:00:00    445
 1959-06-29 01:00:00    NaN
 1959-06-29 02:00:00    NaN
 1959-06-29 03:00:00    NaN
 ...
 1960-06-29 23:00:00    NaN
 1960-06-30 00:00:00    522
 Freq: H, Name: closingPrice, Length: 8809

我们也可以将asfreq方法应用于PeriodPeriodIndex对象,类似于我们对datetimeTimestamp对象所做的操作。 PeriodPeriodIndex稍后介绍,用于表示时间间隔。

asfreq方法接受一个方法参数,该参数允许您向前填充(ffill)或向后填充空白,类似于fillna

In [140]: ibmTS.asfreq('H', method='ffill')
Out[140]: 1959-06-29 00:00:00    445
 1959-06-29 01:00:00    445
 1959-06-29 02:00:00    445
 1959-06-29 03:00:00    445
 ...
 1960-06-29 23:00:00    522
 1960-06-30 00:00:00    522
 Freq: H, Name: closingPrice, Length: 8809

数据重采样

TimeSeries.resample函数使我们能够基于采样间隔和采样函数来聚合/聚合更多粒度数据。

下采样是源自数字信号处理的术语,是指降低信号的采样率的过程。 对于数据,我们使用它来减少我们希望处理的数据量。

相反的过程是上采样,该过程用于增加要处理的数据量,并且需要进行插值以获得中间数据点。 有关下采样和上采样的更多信息,请参考上采样和下采样的实际应用用于视觉表示的下采样时间序列

在这里,我们检查了一些滴答数据以用于重采样。 在检查数据之前,我们需要进行准备。 通过这样做,我们将学习一些有关时间序列数据的有用技术,如下所示:

  • 时间戳
  • 时区处理

这是一个使用滴答数据作为 2014 年 5 月 27 日星期二的 Google 股票价格的示例:

In [150]: googTickData=pd.read_csv('./GOOG_tickdata_20140527.csv')
In [151]: googTickData.head()
Out[151]:     Timestamp   close    high    low   open   volume
 0    1401197402  555.008 556.41  554.35 556.38   81100
 1    1401197460  556.250 556.30  555.25 555.25   18500
 2    1401197526  556.730 556.75  556.05 556.39   9900
 3    1401197582  557.480 557.67  556.73 556.73   14700
 4    1401197642  558.155 558.66  557.48 557.59   15700
 5 rows 6 columns

可以在这个链接中找到先前数据的源。

从上一节中可以看到,我们有一个“时间戳”列,以及收盘价,最高价,最低价和开盘价以及 Google 股票交易量的列。

那么,为什么“时间戳”列看起来有点奇怪? 好吧,滴答数据时间戳通常以纪元时间表示(有关更多信息,请参考这里),作为一种更紧凑的存储方式。 我们需要将其转换为更易于理解的时间,我们可以按照以下步骤进行操作:

In [201]: googTickData['tstamp']=pd.to_datetime(googTickData['Timestamp'],unit='s',utc=True)

In [209]: googTickData.head()
Out[209]:
 Timestamp   close   high   low    open   volume tstamp
 0  14011974020 555.008 556.41 554.35 556.38 81100 2014-05-27 13:30:02
 1  1401197460  556.250 556.30 555.25 555.25 18500 2014-05-27 13:31:00
 2  1401197526  556.730 556.75 556.05 556.39 9900  2014-05-27 13:32:06
 3  1401197582  557.480 557.67 556.73 556.73 14700 2014-05-27 13:33:02
 4  1401197642  558.155 558.66 557.48 557.59 15700 2014-05-27 13:34:02
 5 rows 7 columns

现在,我们想将tstamp列作为索引,并消除纪元Timestamp列:

In [210]: googTickTS=googTickData.set_index('tstamp')
 googTickTS=googTickTS.drop('Timestamp',axis=1)
 googTickTS.head()
Out[210]: 
 tstamp                 close    high    low     open     volume
 2014-05-27 13:30:02    555.008  556.41  554.35  556.38   811000
 2014-05-27 13:31:00    556.250  556.30  555.25  555.25   18500
 2014-05-27 13:32:06    556.730  556.75  556.05  556.39   9900
 2014-05-27 13:33:02    557.480  557.67  556.73  556.73   14700
 2014-05-27 13:34:02    558.155  558.66  557.48  557.59   15700
 5 rows 5 columns

请注意,tstamp索引列的时间以 UTC 为单位,我们可以使用tz_localizetz_convert这两个运算符将其转换为美国/东部时间:

In [211]: googTickTS.index=googTickTS.index.tz_localize('UTC').tz_convert('US/Eastern')

In [212]: googTickTS.head()
Out[212]: 
 tstamp                     close    high    low     open   volume 
 2014-05-27 09:30:02-04:00  555.008  556.41  554.35  556.38  81100
 2014-05-27 09:31:00-04:00  556.250  556.30  555.25  555.25  18500
 2014-05-27 09:32:06-04:00  556.730  556.75  556.05  556.39   9900
 2014-05-27 09:33:02-04:00  557.480  557.67  556.73  556.73  14700
 2014-05-27 09:34:02-04:00  558.155  558.66  557.48  557.59  15700
 5 rows 5 columns

In [213]: googTickTS.tail()
Out[213]:
 tstamp                       close     high   low    open    volume
 2014-05-27 15:56:00-04:00    565.4300  565.48 565.30 565.385  14300
 2014-05-27 15:57:00-04:00    565.3050  565.46 565.20 565.400  14700
 2014-05-27 15:58:00-04:00    565.1101  565.31 565.10 565.310  23200
 2014-05-27 15:59:00-04:00    565.9400  566.00 565.08 565.230  55600
 2014-05-27 16:00:00-04:00    565.9500  565.95 565.95 565.950 126000
 5 rows 5 columns

In [214]: len(googTickTS)
Out[214]: 390

从前面的输出中,我们可以看到交易日中每分钟的滴答声-从股市开盘的上午 9:30 到闭市的下午 4:00。 由于在上午 9:30 和下午 4:00 之间有 390 分钟的时间,因此该数据集中有 390 行。

假设我们要每 5 分钟而不是每分钟获取一次快照? 我们可以通过如下使用降采样来实现:

In [216]: googTickTS.resample('5Min').head(6)
Out[216]:           close      high   low    open       volume    tstamp
2014-05-27 09:30:00-04:00 556.72460 557.15800 555.97200 556.46800 27980
2014-05-27 09:35:00-04:00 556.93648 557.64800 556.85100 557.34200  24620
2014-05-27 09:40:00-04:00 556.48600 556.79994 556.27700 556.60678   8620
2014-05-27 09:45:00-04:00 557.05300 557.27600 556.73800 556.96600   9720
2014-05-27 09:50:00-04:00  556.66200  556.93596  556.46400  556.80326  14560
2014-05-27 09:55:00-04:00  555.96580  556.35400  555.85800  556.23600  12400
6 rows 5 columns

用于重采样的默认函数是平均值。 但是,我们还可以指定其他函数,例如最小值,并且可以通过how参数进行重新采样:

In [245]: googTickTS.resample('10Min', how=np.min).head(4)
Out[245]:         close   high      low  open  volume
tstamp
2014-05-27 09:30:00-04:00   555.008  556.3000  554.35  555.25   9900
2014-05-27 09:40:00-04:00   556.190  556.5600  556.13  556.35   3500
2014-05-27 09:50:00-04:00   554.770  555.5500  554.77  555.55   3400
2014-05-27 10:00:00-04:00   554.580  554.9847  554.45  554.58   1800

可以将各种函数名称传递给how参数,例如sumohlcmaxminstdmeanmedianfirstlast

ohlc函数根据时间序列数据返回开-高-低-关值; 第一个,最大,最小和最后一个值。 要指定关闭左间隔还是右间隔,我们可以按以下方式传递closed参数:

In [254]: pd.set_option('display.precision',5)
 googTickTS.resample('5Min', closed='right').tail(3)
Out[254]:                   close     high  low     open       volume
tstamp
2014-05-27 15:45:00-04:00   564.3167  564.3733   564.1075  564.1700  12816.6667
2014-05-27 15:50:00-04:00   565.1128  565.1725   565.0090  565.0650  13325.0000
2014-05-27 15:55:00-04:00   565.5158  565.6033   565.3083  565.4158  40933.3333
3 rows 5 columns

因此,在前面的命令中,我们可以看到最后一行在 15:55 而不是 16:00 处显示了滴答声。

对于上采样,我们需要指定一种填充方法,以确定如何通过fill_method参数填充间隙:

In [263]: googTickTS[:3].resample('30s', fill_method='ffill')
Out[263]:     close    high     low    open  volume    tstamp
 2014-05-27 09:30:00-04:00   555.008  556.41  554.35  556.38   81100
 2014-05-27 09:30:30-04:00   555.008  556.41  554.35  556.38   81100
 2014-05-27 09:31:00-04:00   556.250  556.30  555.25  555.25   18500
 2014-05-27 09:31:30-04:00   556.250  556.30  555.25  555.25   18500
 2014-05-27 09:32:00-04:00   556.730  556.75  556.05  556.39   9900
 5 rows 5 columns

In [264]: googTickTS[:3].resample('30s', fill_method='bfill')
Out[264]:
 close     high    low  open  volume     tstamp
 2014-05-27 09:30:00-04:00  555.008   556.41  554.35  556.38   81100
 2014-05-27 09:30:30-04:00  556.250   556.30  555.25  555.25   18500
 2014-05-27 09:31:00-04:00  556.250   556.30  555.25  555.25   18500
 2014-05-27 09:31:30-04:00  556.730   556.75  556.05  556.39   9900
 2014-05-27 09:32:00-04:00  556.730   556.75  556.05  556.39   9900
 5 rows 5 columns

不幸的是,fill_method参数当前仅支持两种方法-前向填充和后向填充。 插值方法将很有价值。

时间序列频率的别名

要指定偏移量,可以使用许多别名。 一些最常用的方法如下:

  • B, BM:代表工作日,工作月。 这些是一个月的工作日,即不是假日或周末的任何一天。
  • D, W, M, Q, A:代表日历日,周,月,季度,年末。
  • H, T, S, L, U:代表小时,分钟,秒,毫秒和微秒。

这些别名也可以组合。 在以下情况下,我们每 7 分钟 30 秒重新采样一次:

In [267]: googTickTS.resample('7T30S').head(5)
Out[267]:
 close     high   low   open    volume 
tstamp
2014-05-27 09:30:00-04:00 556.8266 557.4362 556.3144 556.8800 28075.0
2014-05-27 09:37:30-04:00 556.5889 556.9342 556.4264 556.7206 11642.9
2014-05-27 09:45:00-04:00 556.9921 557.2185 556.7171 556.9871  9800.0
2014-05-27 09:52:30-04:00 556.1824 556.5375 556.0350 556.3896 14350.0
2014-05-27 10:00:00-04:00 555.2111 555.4368 554.8288 554.9675 12512.5
5 rows x 5 columns

可以将后缀应用于频率别名,以指定在频率周期中何时开始。 这些称为锚定偏移量:

  • W-SUN, MON, ... DEC,例如,W-TUE表示从星期二开始的每周频率。
  • Q-JAN, FEB, ... DEC,例如,Q-MAY表示每年 5 月底的季度频率。
  • A-JAN, FEB, ... DEC,例如,A-MAY表示每年的频率,到 5 月结束。

这些偏移量可用作date_rangebdate_range函数的参数,以及用作PeriodIndexDatetimeIndex等索引类型的构造器。 可以在 Pandas 文档 中找到对此的全面讨论。

时间序列的概念和数据类型

处理时间序列时,必须考虑两个主要概念:时间点和范围或时间跨度。 在 Pandas 中,前者由时间戳数据类型表示,该数据类型等效于 Python 的datatime.datetimedatetime)数据类型,并且可以互换。 后者(时间跨度)由时间段数据类型表示,该数据类型特定于 Pandas。

这些数据类型均具有与之关联的索引数据类型:Timestamp / DatetimeDatetimeIndexPeriodPeriodIndex。 这些索引数据类型基本上是numpy.ndarray的子类型,包含对应的时间戳和时间段数据类型,并且可用作序列和数据帧对象的索引。

时间段和时间段索引

Period数据类型用于表示时间范围或时间跨度。 这里有一些例子:

# Period representing May 2014
In [287]: pd.Period('2014', freq='A-MAY')
Out[287]: Period('2014', 'A-MAY')

# Period representing specific day – June 11, 2014
In [292]: pd.Period('06/11/2014')
Out[292]: Period('2014-06-11', 'D')

# Period representing 11AM, Nov 11, 1918 
In [298]: pd.Period('11/11/1918 11:00',freq='H')
Out[298]: Period('1918-11-11 11:00', 'H')

我们可以向Periods添加整数,以使时间段提前所需的频率单位数:

In [299]: pd.Period('06/30/2014')+4
Out[299]: Period('2014-07-04', 'D')

In [303]: pd.Period('11/11/1918 11:00',freq='H') - 48
Out[303]: Period('1918-11-09 11:00', 'H')

我们还可以计算两个Periods之间的差,并返回它们之间的频率单位数:

In [304]: pd.Period('2014-04', freq='M')-pd.Period('2013-02', freq='M')
Out[304]: 14

时间段索引

可以通过两种方式创建PeriodIndex对象,该对象是Period对象的index类型。

  1. 使用period_range函数从一些period对象中,类似于date_range

    In [305]: perRng=pd.period_range('02/01/2014','02/06/2014',freq='D')
     perRng
    Out[305]: <class 'pandas.tseries.period.PeriodIndex'>
     freq: D
     [2014-02-01, ..., 2014-02-06]
     length: 6
    
    In [306]: type(perRng[:2])
    Out[306]: pandas.tseries.period.PeriodIndex
    
    In [307]: perRng[:2]
    Out[307]: <class 'pandas.tseries.period.PeriodIndex'>
     freq: D
     [2014-02-01, 2014-02-02]

    正如我们从前面的命令可以确认的那样,当您拉开盖子时,PeriodIndex函数实际上就是下面的Period对象的ndarray

  2. 也可以通过直接调用Period构造器来完成:

    In [312]: JulyPeriod=pd.PeriodIndex(['07/01/2014','07/31/2014'], freq='D')
     JulyPeriod
    Out[312]: <class 'pandas.tseries.period.PeriodIndex'>
     freq: D
     [2014-07-01, 2014-07-31]

从前面的输出中可以看出,这两种方法之间的差异是period_range填充了结果ndarray,但是Period构造器没有填充,您必须指定索引中应该包含的所有值。

时间序列数据类型之间的转换

我们可以通过to_periodto_timestamp函数将PeriodPeriodIndex数据类型转换为Datetime / TimestampDatetimeIndex数据类型,如下所示:

In [339]: worldCupFinal=pd.to_datetime('07/13/2014', 
 errors='raise')
 worldCupFinal
 Out[339]: Timestamp('2014-07-13 00:00:00')

In [340]: worldCupFinal.to_period('D')
 Out[340]: Period('2014-07-13', 'D')

In [342]: worldCupKickoff=pd.Period('06/12/2014','D')
 worldCupKickoff
Out[342]: Period('2014-06-12', 'D')
In [345]: worldCupKickoff.to_timestamp()
Out[345]: Timestamp('2014-06-12 00:00:00', tz=None)

In [346]: worldCupDays=pd.date_range('06/12/2014',periods=32, 
 freq='D')
 worldCupDays
Out[346]: <class 'pandas.tseries.index.DatetimeIndex'>
 [2014-06-12, ..., 2014-07-13]
 Length: 32, Freq: D, Timezone: None

In [347]: worldCupDays.to_period()
Out[347]: <class 'pandas.tseries.period.PeriodIndex'>
 freq: D
 [2014-06-12, ..., 2014-07-13]
 length: 32

与时间序列相关的对象的摘要

下表总结了与时间序列有关的对象:

对象 摘要
datetime.datetime 这是一个标准的 Python datetime
Timestamp 这是源自datetime.datetime的 Pandas 类
DatetimeIndex 这是一个 Pandas 类,实现为Timestamp / datetime对象的不可变numpy.ndarray
Period 这是代表某个时间段的 Pandas 类
PeriodIndex 这是一个 Pandas 类,实现为Period对象的不可变numpy.ndarray
timedelta 这是一个 Python 类,表示两个datetime.datetime实例之间的差异。 实现为datetime.timedelta
relativedelta 实现为dateutil.relativedeltadateutil是标准 Python datetime模块的扩展。 它提供了额外的功能,例如以大于 1 天的单位表示的时间增量。
DateOffset 这是一个表示常规频率增量的 Pandas 类。 它具有与dateutil.relativedelta相似的功能。

matplotlib 绘图

本节简要介绍了如何使用matplotlib在 Pandas 中进行绘图。 matplotlib api使用标准约定导入,如以下命令所示:

In [1]: import matplotlib.pyplot as plt

序列和数据帧有一个plot方法,它只是plt.plot的包装。 在这里,我们将研究如何绘制正弦和余弦函数的简单图。 假设我们希望在-pipi间隔上绘制以下函数:

  • f(x) = cos(x) + sin(x)
  • g(x) = cos(x) - sin(x)

这给出了以下间隔:

In [51]: import numpy as np
In [52]: X = np.linspace(-np.pi, np.pi, 256,endpoint=True)

In [54]: f,g = np.cos(X)+np.sin(X), np.sin(X)-np.cos(X)
In [61]: f_ser=pd.Series(f)
 g_ser=pd.Series(g)

In [31]: plotDF=pd.concat([f_ser,g_ser],axis=1)
 plotDF.index=X
 plotDF.columns=['sin(x)+cos(x)','sin(x)-cos(x)']
 plotDF.head()
Out[31]:  sin(x)+cos(x)  sin(x)-cos(x)
-3.141593  -1.000000   1.000000
-3.116953  -1.024334   0.975059
-3.092313  -1.048046   0.949526
-3.067673  -1.071122   0.923417
-3.043033  -1.093547   0.896747
5 rows × 2 columns

现在,我们可以使用plot()命令和plt.show()命令绘制数据帧来显示它:

In [94]: plotDF.plot()
 plt.show()

We can apply a title to the plot as follows:
In [95]: plotDF.columns=['f(x)','g(x)']
 plotDF.plot(title='Plot of f(x)=sin(x)+cos(x), \n g(x)=sinx(x)-cos(x)')
 plt.show()

以下是上述命令的输出:

Plotting using matplotlib

我们还可以使用以下命令在不同的子图中分别绘制两个序列(函数):

In [96]: plotDF.plot(subplots=True, figsize=(6,6))
 plt.show()

The following is the output of the preceding command:

Plotting using matplotlib

在 Pandas 中使用matplotlib的绘图函数还有很多。 有关更多信息,请参阅这个链接中的文档。

总结

总而言之,我们讨论了如何处理缺失的数据值以及如何处理 Pandas 中的日期和时间序列。 我们还走了一段简短的弯路,以研究matplotlib在 Pandas 中的绘图功能。 在准备用于分析和预测的干净数据时,处理缺失的数据起着非常重要的作用,而绘制和可视化数据的能力是每个好的数据分析人员工具箱中必不可少的部分。

在下一章中,我们将对真实数据集进行一些基本数据分析,在其中我们将分析并回答有关数据的基本问题。 有关 Pandas 中这些主题的更多参考,请查看官方文档