In [1]: import pandas as pd

In [2]: import matplotlib.pyplot as plt
本教程使用的数据
  • 本教程使用关于 \(NO_2\) 和小于 2.5 微米的颗粒物的空气质量数据,这些数据由 OpenAQ 提供,并使用 py-openaq 包下载。air_quality_no2_long.csv" 数据集分别提供了巴黎、安特卫普和伦敦的测量站 FR04014BETR801London Westminster\(NO_2\) 值。

    原始数据
    In [3]: air_quality = pd.read_csv("data/air_quality_no2_long.csv")
    
    In [4]: air_quality = air_quality.rename(columns={"date.utc": "datetime"})
    
    In [5]: air_quality.head()
    Out[5]: 
        city country                   datetime location parameter  value   unit
    0  Paris      FR  2019-06-21 00:00:00+00:00  FR04014       no2   20.0  µg/m³
    1  Paris      FR  2019-06-20 23:00:00+00:00  FR04014       no2   21.8  µg/m³
    2  Paris      FR  2019-06-20 22:00:00+00:00  FR04014       no2   26.5  µg/m³
    3  Paris      FR  2019-06-20 21:00:00+00:00  FR04014       no2   24.9  µg/m³
    4  Paris      FR  2019-06-20 20:00:00+00:00  FR04014       no2   21.4  µg/m³
    
    In [6]: air_quality.city.unique()
    Out[6]: array(['Paris', 'Antwerpen', 'London'], dtype=object)
    

如何轻松处理时间序列数据#

使用 pandas datetime 属性#

  • 我想将列 datetime 中的日期作为 datetime 对象而不是纯文本来处理

    In [7]: air_quality["datetime"] = pd.to_datetime(air_quality["datetime"])
    
    In [8]: air_quality["datetime"]
    Out[8]: 
    0      2019-06-21 00:00:00+00:00
    1      2019-06-20 23:00:00+00:00
    2      2019-06-20 22:00:00+00:00
    3      2019-06-20 21:00:00+00:00
    4      2019-06-20 20:00:00+00:00
                      ...           
    2063   2019-05-07 06:00:00+00:00
    2064   2019-05-07 04:00:00+00:00
    2065   2019-05-07 03:00:00+00:00
    2066   2019-05-07 02:00:00+00:00
    2067   2019-05-07 01:00:00+00:00
    Name: datetime, Length: 2068, dtype: datetime64[ns, UTC]
    

    最初,datetime 中的值是字符串,不提供任何 datetime 操作(例如,提取年份、星期几等)。通过应用 to_datetime 函数,pandas 会解释字符串并将其转换为 datetime(即 datetime64[ns, UTC])对象。在 pandas 中,我们将这些 datetime 对象称为 pandas.Timestamp,类似于标准库中的 datetime.datetime

注意

由于许多数据集确实包含某一列中的 datetime 信息,因此 pandas 的输入函数,例如 pandas.read_csv()pandas.read_json(),在使用 parse_dates 参数读取数据时,可以通过指定要作为 Timestamp 读取的列列表来完成日期转换。

pd.read_csv("../data/air_quality_no2_long.csv", parse_dates=["datetime"])

为什么这些 pandas.Timestamp 对象很有用?让我们用一些示例来阐述其附加价值。

我们正在处理的时间序列数据集的开始日期和结束日期是什么?

In [9]: air_quality["datetime"].min(), air_quality["datetime"].max()
Out[9]: 
(Timestamp('2019-05-07 01:00:00+0000', tz='UTC'),
 Timestamp('2019-06-21 00:00:00+0000', tz='UTC'))

使用 pandas.Timestamp 表示 datetimes 使我们能够用日期信息进行计算并使其可比较。因此,我们可以用它来获取时间序列的长度

In [10]: air_quality["datetime"].max() - air_quality["datetime"].min()
Out[10]: Timedelta('44 days 23:00:00')

结果是一个 pandas.Timedelta 对象,类似于标准 Python 库中的 datetime.timedelta,定义了时间持续时间。

到用户指南

pandas 支持的各种时间概念在用户指南的时间相关概念部分中进行了解释。

  • 我想为 DataFrame 添加一个新列,其中只包含测量月份

    In [11]: air_quality["month"] = air_quality["datetime"].dt.month
    
    In [12]: air_quality.head()
    Out[12]: 
        city country                  datetime  ... value   unit  month
    0  Paris      FR 2019-06-21 00:00:00+00:00  ...  20.0  µg/m³      6
    1  Paris      FR 2019-06-20 23:00:00+00:00  ...  21.8  µg/m³      6
    2  Paris      FR 2019-06-20 22:00:00+00:00  ...  26.5  µg/m³      6
    3  Paris      FR 2019-06-20 21:00:00+00:00  ...  24.9  µg/m³      6
    4  Paris      FR 2019-06-20 20:00:00+00:00  ...  21.4  µg/m³      6
    
    [5 rows x 8 columns]
    

    通过使用 Timestamp 对象表示日期,pandas 提供了许多时间相关的属性。例如 month,还有 yearquarter 等等。所有这些属性都可以通过 dt 访问器访问。

到用户指南

现有日期属性的概述在时间和日期组件概览表中给出。关于 dt 访问器返回类似 datetime 属性的更多详细信息,在关于dt 访问器的专门部分进行了解释。

  • 每个测量位置的每周各天的平均 \(NO_2\) 浓度是多少?

    In [13]: air_quality.groupby(
       ....:     [air_quality["datetime"].dt.weekday, "location"])["value"].mean()
       ....: 
    Out[13]: 
    datetime  location          
    0         BETR801               27.875000
              FR04014               24.856250
              London Westminster    23.969697
    1         BETR801               22.214286
              FR04014               30.999359
                                      ...    
    5         FR04014               25.266154
              London Westminster    24.977612
    6         BETR801               21.896552
              FR04014               23.274306
              London Westminster    24.859155
    Name: value, Length: 21, dtype: float64
    

    还记得统计计算教程中由 groupby 提供的拆分-应用-合并模式吗?在这里,我们希望计算一个给定的统计量(例如,平均 \(NO_2\)),针对每周的每一天针对每个测量位置。为了按星期几进行分组,我们使用 pandas Timestamp 的 datetime 属性 weekday(星期一为 0,星期日为 6),该属性也可以通过 dt 访问器访问。可以同时按位置和星期几进行分组,以便将平均值计算拆分到这些组合中的每一个上。

    注意

    由于这些示例中我们使用的是非常短的时间序列,因此分析结果不具有长期代表性!

  • 绘制我们时间序列中所有站点一天内典型的 \(NO_2\) 模式图。换句话说,一天中每个小时的平均值是多少?

    In [14]: fig, axs = plt.subplots(figsize=(12, 4))
    
    In [15]: air_quality.groupby(air_quality["datetime"].dt.hour)["value"].mean().plot(
       ....:     kind='bar', rot=0, ax=axs
       ....: )
       ....: 
    Out[15]: <Axes: xlabel='datetime'>
    
    In [16]: plt.xlabel("Hour of the day");  # custom x label using Matplotlib
    
    In [17]: plt.ylabel("$NO_2 (µg/m^3)$");
    
    ../../_images/09_bar_chart.png

    与上一个示例类似,我们想计算一个给定的统计量(例如,平均 \(NO_2\)),针对一天中的每个小时,我们可以再次使用拆分-应用-合并方法。对于这种情况,我们使用 pandas Timestamp 的 datetime 属性 hour,该属性也可以通过 dt 访问器访问。

Datetime 作为索引#

重塑教程中,介绍了 pivot() 方法,用于将数据表重塑,将每个测量位置作为单独的列

In [18]: no_2 = air_quality.pivot(index="datetime", columns="location", values="value")

In [19]: no_2.head()
Out[19]: 
location                   BETR801  FR04014  London Westminster
datetime                                                       
2019-05-07 01:00:00+00:00     50.5     25.0                23.0
2019-05-07 02:00:00+00:00     45.0     27.7                19.0
2019-05-07 03:00:00+00:00      NaN     50.4                19.0
2019-05-07 04:00:00+00:00      NaN     61.9                16.0
2019-05-07 05:00:00+00:00      NaN     72.4                 NaN

注意

通过对数据进行透视,datetime 信息成为表格的索引。通常,可以通过 set_index 函数将一列设置为索引。

使用 datetime 索引(即 DatetimeIndex)提供了强大的功能。例如,我们不需要 dt 访问器来获取时间序列属性,而是可以直接在索引上使用这些属性

In [20]: no_2.index.year, no_2.index.weekday
Out[20]: 
(Index([2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019,
        ...
        2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019],
       dtype='int32', name='datetime', length=1033),
 Index([1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        ...
        3, 3, 3, 3, 3, 3, 3, 3, 3, 4],
       dtype='int32', name='datetime', length=1033))

其他一些优点包括方便地对时间段进行子集划分或调整图表上的时间刻度。让我们将其应用于我们的数据。

  • 绘制不同站点 \(NO_2\) 值从 5 月 20 日到 5 月 21 日结束的图表

    In [21]: no_2["2019-05-20":"2019-05-21"].plot();
    
    ../../_images/09_time_section.png

    通过提供一个可以解析为 datetime 的字符串,可以在 DatetimeIndex 上选择特定的数据子集。

到用户指南

关于 DatetimeIndex 和使用字符串进行切片的更多信息,请参见时间序列索引部分。

将时间序列重采样到另一个频率#

  • 将当前的小时时间序列值聚合为每个站点的每月最大值。

    In [22]: monthly_max = no_2.resample("ME").max()
    
    In [23]: monthly_max
    Out[23]: 
    location                   BETR801  FR04014  London Westminster
    datetime                                                       
    2019-05-31 00:00:00+00:00     74.5     97.0                97.0
    2019-06-30 00:00:00+00:00     52.5     84.7                52.0
    

    在带有 datetime 索引的时间序列数据上,一个非常强大的方法是将时间序列重采样()到另一个频率(例如,将秒级数据转换为 5 分钟级数据)。

resample() 方法类似于 groupby 操作

  • 它通过使用定义目标频率的字符串(例如 M, 5H 等)提供基于时间的组合

  • 它需要一个聚合函数,例如 mean, max 等。

到用户指南

用于定义时间序列频率的别名概述在偏移别名概览表中给出。

定义后,时间序列的频率由 freq 属性提供

In [24]: monthly_max.index.freq
Out[24]: <MonthEnd>
  • 绘制每个站点的每日平均 \(NO_2\) 值图。

    In [25]: no_2.resample("D").mean().plot(style="-o", figsize=(10, 5));
    
    ../../_images/09_resample_mean.png
到用户指南

关于时间序列重采样强大功能的更多详细信息,请参见用户指南中关于重采样的部分。

记住

  • 有效的日期字符串可以使用 to_datetime 函数或作为读取函数的一部分转换为 datetime 对象。

  • pandas 中的 Datetime 对象支持计算、逻辑运算以及使用 dt 访问器的便捷日期相关属性。

  • 一个 DatetimeIndex 包含这些日期相关属性并支持便捷的切片。

  • Resample 是一个改变时间序列频率的强大方法。

到用户指南

关于时间序列的完整概述可在时间序列和日期功能页面上找到。