百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Python 数据分析——Pandas 时间序列

wptr33 2025-07-08 23:40 3 浏览

Pandas提供了表示时间点、时间段和时间间隔等三种与时间有关的类型,以及元素为这些类型的索引对象,并提供了许多时间序列相关的函数。

一、时间点、时间段、时间间隔

Timestamp对象从Python标准库中的datetime类继承,表示时间轴上的一个时刻。它提供了方便的时区转换功能。下面调用Timestamp.now()获取当前时间now,它是不包含时区信息的本地时间。调用其tz_localize()可以得到指定时区的Timestamp对象。而带时区信息的Timestamp对象可以通过其tz_convert()转换时区。下面的now_shanghai的时间以"+08:00"结尾,表示它是东八区的时间,将其转换为东京时间得到now_tokyo,它是东九区的时间:

now = pd.Timestamp.now()
now_shanghai = now.tz_localize("Asia/Shanghai")
now_tokyo = now_shanghai.tz_convert("Asia/Tokyo")
print u"本地时间:", now
print u"上海时区:", now_shanghai
print u"东京时区:", now_tokyo
本地时间: 2015-07-25 11:50:46.264000
上海时区: 2015-07-25 11:50:46.264000+08:00
东京时区: 2015-07-25 12:50:46.264000+09:00

不同时区的时间可以比较,而本地时间和时区时间无法比较:

now_shanghai == now_tokyo
True

通过pytz模块的common_timezones()可以获得常用的表示时区的字符串:

import pytz
pytz.common_timezones
['Africa/Abidjan',
'Africa/Accra',
'Africa/Addis_Ababa',
'Africa/Algiers',
...

Period对象表示一个标准的时间段,例如某年、某月、某日、某小时等。时间段的长短由freq属性决定。下面的程序调用Period.now(),分别获得包含当前时间的日周期时间段和小时周期时间段。

now_day = pd.Period.now(freq="D")
now_hour = pd.Period.now(freq="H")
now_day                       now_hour           
-------------------------  -------------------------------
Period('2015-07-25', 'D')  Period('2015-07-25 11:00', 'H')

freq属性是一个描述时间段的字符串,其可选值可以通过下面的代码获得:

from pandas.tseries import frequencies

frequencies._period_code_map.keys()

frequencies._period_alias_dictionary()

对于周期为年度和星期的时间段,可以通过freq指定开始的时间。例如"W"表示以星期天开始的星期时间段,而"W-MON"则表示以星期一开始的星期时间段:

now_week_sun = pd.Period.now(freq="W")
now_week_mon = pd.Period.now(freq="W-MON")
now_week_sun                              now_week_mon              
----------------------------------------  ----------------------------------------
Period('2015-07-20/2015-07-26', 'W-SUN')  Period('2015-07-21/2015-07-27', 'W-MON')

时间段的起点和终点可以通过start_time和end_time属性获得,它们都是表示时间点的Timestamp对象:

now_day.start_time                      now_day.end_time             
--------------------------------  ------------------------------------------
Timestamp('2015-07-25 00:00:00')  Timestamp('2015-07-25 23:59:59.999999999')

调用Timestamp对象的to_period()方法可以把时间点转换为包含该时间点的时间段。注意时间段不包含时区信息:

now_shanghai.to_period("H")
Period('2015-07-25 11:00', 'H')

Timestamp和Period对象可以通过其属性获得年、月、日等信息。下面分别获得年、月、日、星期几、一年中的第几天、小时等信息:

将两个时间点相减可以得到表示时间间隔的Timedelta对象,下面计算当前时刻离2015年国庆节还有多少时间:

national_day = pd.Timestamp("2015-10-1")
td = national_day - pd.Timestamp.now()
td
Timedelta('67 days 12:09:04.039000')

时间点和时间间隔之间可以进行加减运算:

national_day + pd.Timedelta("20 days 10:20:30")
Timestamp('2015-10-21 10:20:30')

Timedelta对象的days、seconds、microseconds和nanoseconds等属性分别获得它包含的天数、秒数、微秒数和纳秒数。注意这些值与对应的单位相乘并求和才是该对象表示的总时间间隔:

td.days  td.seconds  td.microseconds
-------  ----------  ---------------
67L     43744L      39000L  

也可以通过关键字参数直接指定时间间隔的天数、小时数、分钟数和秒数:

print pd.Timedelta(days=10, hours=1, minutes=2, seconds=10.5)
print pd.Timedelta(seconds=100000)
10 days 01:02:10.500000
1 days 03:46:40

二、时间序列

上节介绍的Timestamp、Period和Timedelta对象都是表示单个值的对象,这些值可以放在索引或数据列中。下面的程序调用random_timestamps()创建一个包含5个随机时间点的DatetimeIndex对象ts_index,然后通过ts_index创建PeriodIndex类型的索引对象pd_index和TimedeltaIndex类型的索引对象td_index。DatetimeIndex、PeriodIndex和TimedeltaIndex都从Index继承,可以作为Series或DataFrame的索引。

random_timestamps()中的date_range()函数创建以start为起点、以end为终点、周期为freq的DatetimeIndex对象。

下面查看这三种索引对象的dtype属性。其中M8[ns]和m8[ns]是NumPy中表示时间点和时间间隔的dtype类型,内部采用64位整数存储时间信息,其中[ns]表示时间的最小单位为纳秒,能表示的时间范围大约是公元1678年到公元 2262年。PeriodIndex也使用64位整数,但是最小时间单位由其freq属性决定。

ts_index.dtype   pd_index.dtype   td_index.dtype
----------------  --------------  ----------------
dtype('<M8[ns]')  dtype('int64')  dtype('<m8[ns]')

这三种索引对象都提供了许多与时间相关的属性,例如:

ts_index.weekday   pd_index.month        td_index.seconds      
----------------  ---------------  ----------------------------
[3, 6, 5, 3, 1]   [1, 2, 2, 8, 8]  [57120, 15960, 51000, 37980]

DatetimeIndex.shift(n, freq)可以移动时间点,将当前的时间移动n个freq时间单位。对于天数、小时这样的精确单位,结果相当于与指定的时间间隔相加:

ts_index.shift(1, "H")
DatetimeIndex(['2015-01-15 17:12:00', '2015-02-15 09:04:00',
'2015-02-28 13:30:00', '2015-08-06 03:40:00',
'2015-08-18 14:13:00'],
dtype='datetime64[ns]', freq=None, tz=None)

而对于月份这样不精确的时间单位,则移动一个单位相当于移到月头或月底:

ts_index.shift(1, "M")
DatetimeIndex(['2015-01-31 16:12:00', '2015-02-28 08:04:00',
'2015-03-31 12:30:00', '2015-08-31 02:40:00',
'2015-08-31 13:13:00'],
dtype='datetime64[ns]', freq=None, tz=None)

DatetimeIndex.normalize()将时刻修改为当天的凌晨零点,可以理解为按日期取整:

ts_index.normalize()
DatetimeIndex(['2015-01-15', '2015-02-15', '2015-02-28', '2015-08-06',
'2015-08-18'],
dtype='datetime64[ns]', freq=None, tz=None)

如果希望对任意的时间周期取整,可以先通过to_period()将其转换为PeriodIndex对象,然后再调用to_timestamp()方法转换回DatetimeIndex对象。to_timestamp()的how参数决定将时间段的起点还是终点转换为时间点,默认值为"start"。

ts_index.to_period("H").to_timestamp()
DatetimeIndex(['2015-01-15 16:00:00', '2015-02-15 08:00:00',
'2015-02-28 12:00:00', '2015-08-06 02:00:00',
'2015-08-18 13:00:00'],
dtype='datetime64[ns]', freq=None, tz=None)

下面的Series对象ts_series的索引为DatetimeIndex对象,这种Series对象被称为时间序列:

ts_series = pd.Series(range(5), index=ts_index)

时间序列提供一些专门用于处理时间的方法,例如between_time()返回所有位于指定时间范围之内的数据:

ts_series.between_time("9:00", "18:00")
2015-01-15 16:12:00    0
2015-02-28 12:30:00    2
2015-08-18 13:13:00    4
dtype: int64

而tshift()则将索引移动指定的时间:

ts_series.tshift(1, freq="D")
2015-01-16 16:12:00    0
2015-02-16 08:04:00    1
2015-03-01 12:30:00    2
2015-08-07 02:40:00    3
2015-08-19 13:13:00    4
dtype: int64

以PeriodIndex和TimedeltaIndex为索引的序列也可以使用tshift()对索引进行移动:

时间信息除了可以作为索引之外,还可以作为Series或DataFrame的列。下面分别将上述三种索引对象转换为Series对象,并查看其dtype属性:

可以看到Pandas的Series对象目前尚不支持使用64位整数表示时间段,因此使用对象数组保存所有的Period对象。而对于时间点和时间间隔数据则采用64位整数数组保存。

当序列的值为时间数据时,可以通过名字空间对象dt调用时间相关的属性和方法。例如:

三、与NaN相关的函数

Pandas使用NaN表示缺失的数据,由于整数列无法使用NaN,因此如果整数类型的列出现缺失数据,则会被自动转换为浮点数类型。下面将布尔类型的DataFrame对象传递给一个整数类型的DataFrame对象的where()方法。该方法将False对象的元素设置为NaN,注意其结果变成了浮点数类型,而没有NaN的列仍然为整数类型。

isnull()和notnull()用于判断元素值是否为NaN,它们返回全是布尔值的DataFrame对象。df.notnull()和~df.isnull()的结果相同,但是由于notnull()少创建一个临时对象,其运算效率更高一些。

count()返回每行或每列的非NaN元素的个数:

对于包含NaN元素的数据,最简单的办法就是调用dropna()以删除包含NaN的行或列,当全部使用默认参数时,将删除包含NaN的所有行。可以通过thresh参数指定NaN个数的阈值,删除所有NaN个数大于等于该阈值的行。

当行数据按照某种物理顺序(例如时间)排列时,可以使用NaN前后的数据对其进行填充。ffill()使用之前的数据填充,而bfill()则使用之后的数据填充。interpolate()使用前后数据进行插值填充:

interpolate()默认使用等距线性插值,可以通过其method参数指定插值算法。在下面的例子中,第0个元素和第2个元素的数值分别为3.0和7.0,因此当method参数默认省时下标为1的NaN被填充为前后两个元素的平均值5.0。而当method为"index"时,则使用索引值进行插值运算。由于第1个元素的索引与第2个元素的索引接近,因此其插值结果也接近第二个元素的值。

此外还可以使用字典参数让fillna()对不同的列使用不同的值填充NaN:

各种聚合方法的skipna参数默认为True,因此计算时将忽略NaN元素,注意每行或每列是单独运算的。如果需要忽略包含NaN的整行,需要先调用dropna()。若将skipna参数设置为False,则包含NaN的行或列的运算结果为NaN。

df.combine_first(other)使用other填充df中的NaN元素。它将df中的NaN元素替换为other中对应标签的元素。在下面的例子中,df_nan中索引为1、2、8、9的行中的NaN被替换为df_other中相应的值:


四、改变DataFrame的形状

本节介绍表1中的函数:

表1 本节要介绍的函数

函数名

功能

函数名

功能

concat

拼接多块数据

drop

删除行或列

set_index

设置索引

reset_index

将行索引转换为列

stack

将列索引转换为行索引

unstack

将行索引转换为列索引

reorder_levels

设置索引级别的顺序

swaplevel

交换索引中两个级别的顺序

sort_index

对索引排序

pivot

创建透视表

melt

透视表的逆变换

assign

返回添加新列之后的数据

DataFrame的shape属性和NumPy的二维数组相同,是一个有两个元素的元组。由于DataFrame的index和columns都支持MultiIndex索引对象,因此可以用DataFrame表示更高维的数据。

下面首先从CSV文件读入数据,并使用groupby()计算分组的平均值。关于groupby在后面的章节还会详细介绍。注意下面的soils_mean对象的行索引是多级索引:

1.添加删除列或行

由于DataFrame可以看作一个Series对象的字典,因此通过DataFrame[colname] = values即可添加新列。有时新添加的列是从已经存在的列计算而来,这时可以使用eval()方法计算。例如下面的代码添加一个名为N_percent的新列,其值为N列乘上100:

soils["N_percent"] = soils.eval("N * 100")

assign()方法添加由关键字参数指定的列,它返回一个新的DataFrame对象,原数据的内容保持不变:

append()方法用于添加行,它没有inplace参数,只能返回一个全新对象。由于每次调用append()都会复制所有的数据,因此在循环中使用append()添加数据会极大地降低程序的运算速度。可以使用一个列表缓存所有的分块数据,然后调用concat()将所有这些数据沿着指定轴拼贴到一起。下面的程序比较二者的运算速度:

def random_dataframe(n):
columns = ["A", "B", "C"]
for i in range(n):
nrow = np.random.randint(10, 20)
yield pd.DataFrame(np.random.randint(0, 100, size=(nrow, 3)), columns=columns)

df_list = list(random_dataframe(1000))
%%time
df_res1 = pd.DataFrame([])
for df in df_list:
df_res1 = df_res1.append(df)
Wall time: 1.37 s
%%time
df_res2 = pd.concat(df_list, axis=0)
Wall time: 118 ms

可以使用keys参数指定与每块数据对应的键,这样结果中的拼接轴将使用多级索引,方便快速获取原始的数据块。下面获取拼接之后的DataFrame对象中第0级标签为30的数据,并使用equals()方法判断它是否与原始数据中下标为30的数据块相同:

df_res3 = pd.concat(df_list, axis=0, keys=range(len(df_list)))
df_res3.loc[30].equals(df_list[30])
True

drop()删除指定标签对应的行或列,下面删除名为N和Group的两列:

2.行索引与列之间的相互转换

reset_index()可以将索引转换为列,通过level参数可以指定被转换为列的级别。如果只希望从索引中删除某个级别,可以设置drop参数为True。

set_index()将列转换为行索引,如果append参数为False(默认值),则删除当前的行索引;若为True,则为当前的索引添加新的级别。


3.行索引和列索引的相互转换

stack()方法把指定级别的列索引转换为行索引,而unstack()则把行索引转换为列索引。下面的程序将行索引中的第一级转换为列索引的第一级,所得到的结果中行索引为单级索引,而列索引为多级索引:

无论是stack()还是unstack(),当所有的索引被转换到同一个轴上时,将得到一个Series对象:

4.交换索引的等级

reorder_levels()和swaplevel()交换指定轴的索引级别。下面调用swaplevel()交换行索引的两个级别,然后调用sort_index()对新的索引进行排序:


5.透视表

pivot()可以将DataFrame中的三列数据分别作为行索引、列索引和元素值,将这三列数据转换为二维表格:

pivot()的三个参数index、columns和values只支持指定一列数据。若不指定values参数,就将剩余的列都当作元素值列,得到多级列索引:

melt()可以看作pivot()的逆变换。由于它不能对行索引进行操作,因此先调用reset_index()将行索引转换为列,然后用id_vars参数指定该列为标识列:

相关推荐

突然崩了!很多人以为电脑坏了,腾讯紧急回应

今天(24日)上午,多名网友反应,收到QQ遇到错误的消息,#QQ崩了#登上热搜。有网友表示:“一直在重新登录,以为是电脑的问题”@腾讯QQ发微博致歉:今天11点左右,有少量用户使用桌面QQ时出现报错...

Excel八大常见错误值全解析,从此告别乱码烦恼~

我是【桃大喵学习记】,欢迎大家关注哟~,每天为你分享职场办公软件使用技巧干货!——首发于微信号:桃大喵学习记日常工作中很多小伙伴经常被Excel报错困扰,#N/A、#VALUE!、#REF!...这些...

Excel中#NAME?错误详解,新手必看!

你是不是在输入函数时,突然看到#NAME?报错,完全不懂哪里出问题?本篇小红书文章,一次讲清楚【#NAME?】错误的4大常见原因+对应解决方法!什么是#NAME?错误?当Excel...

Rust错误处理秒变简单!anyhow和thiserror就像你的贴心小助手

导语:遇到Rust错误提示就像看天书?别慌!anyhow和thiserror就像翻译官+小秘书组合,把混乱的错误信息变成人话,还能帮你记录出错现场!一、错误处理为什么烦人?(就像迷路没导航)...

Excel中#DIV/0!错误详解,新手避坑指南

在用Excel做计算时,常常会遇到#DIV/0!报错,特别是涉及除法的时候。这篇文章帮你搞懂出现这个错误的原因,附上实用的解决方法什么是#DIV/0!错误?#DIV/0!=除数是0...

Excel中#VALUE!错误详解,新手秒懂!

你是不是经常在Excel中遇到#VALUE!报错,却不知道为什么?今天这篇小红书文章,一次性讲清楚【#VALUE!】的出现原因+解决方法!什么是#VALUE!错误?#VALUE!是...

30天学会Python编程:24. Python设计模式与架构

24.1设计模式基础24.1.1设计模式分类24.1.2SOLID原则...

Python学不会来打我(25)函数参数传递详解:值传递?引用传递?

在Python编程中,函数参数的传递机制...

30天学会Python编程:20. Python网络爬虫简介

20.1网络爬虫基础20.1.1爬虫定义与原理20.1.2法律与道德规范表19-1爬虫合法性要点...

「ELK」elastalert 日志告警(elk日志平台)

一、环境系统:centos7elk版本:7.6.21.1ElastAlert工作原理...

让你的Python代码更易读:7个提升函数可读性的实用技巧

如果你正在阅读这篇文章,很可能你已经用Python编程有一段时间了。今天,让我们聊聊可以提升你编程水平的一件事:编写易读的函数。...

Python常见模块机os、sys、pickle、json、time用法

1.os模块:提供与操作系统交互的功能。importos#获取当前工作目录current_dir=os.getcwd()#创建新目录os.mkdir("new_direc...

当心!Python中的这个高效功能,可能让你的代码“裸奔”?

如果你经常用Python,一定对F-strings不陌生——它简洁、高效,一行代码就能让字符串和变量无缝拼接,堪称“代码美颜神器”。但你知道吗?这个看似人畜无害的功能,如果使用不当,可能会让你的程序“...

xmltodict,一个有趣的 Python 库!

大家好,今天为大家分享一个有趣的Python库-xmltodict。...

如何用Python写一个自动备份脚本(备份列表python)

今天想整个自动备份脚本,用到schedule模块,这个模块是三方库,所有我们就要安装下,没有的模块,显示的颜色就不一样,不同编辑工具显示颜色不一样,这里是vs显示灰白色吧。...