Python 数据分析——Pandas 时间序列
wptr33 2025-07-08 23:40 5 浏览
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参数指定该列为标识列:
相关推荐
- SQL轻松入门(5):窗口函数(sql语录中加窗口函数的执行)
-
01前言标题中有2个字让我在初次接触窗口函数时,真真切切明白了何谓”高级”?说来也是一番辛酸史!话说,我见识了窗口函数的强大后,便磨拳擦掌的要试验一番,结果在查询中输入语句,返回的结果却是报错,Wh...
- 28个SQL常用的DeepSeek提示词指令,码住直接套用
-
自从DeepSeek出现后,极大地提升了大家平时的工作效率,特别是对于一些想从事数据行业的小白,只需要掌握DeepSeek的提问技巧,SQL相关的问题也不再是个门槛。...
- 从零开始学SQL进阶,数据分析师必备SQL取数技巧,建议收藏
-
上一节给大家讲到SQL取数的一些基本内容,包含SQL简单查询与高级查询,需要复习相关知识的同学可以跳转至上一节,本节给大家讲解SQL的进阶应用,在实际过程中用途比较多的子查询与窗口函数,下面一起学习。...
- SQL_OVER语法(sql语句over什么含义)
-
OVER的定义OVER用于为行定义一个窗口,它对一组值进行操作,不需要使用GROUPBY子句对数据进行分组,能够在同一行中同时返回基础行的列和聚合列。...
- SQL窗口函数知多少?(sql窗口怎么执行)
-
我们在日常工作中是否经常会遇到需要排名的情况,比如:每个部门按业绩来排名,每人按绩效排名,对部门销售业绩前N名的进行奖励等。面对这类需求,我们就需要使用sql的高级功能——窗口函数。...
- 如何学习并掌握 SQL 数据库基础:从零散查表到高效数据提取
-
无论是职场数据分析、产品运营,还是做副业项目,掌握SQL(StructuredQueryLanguage)意味着你能直接从数据库中提取、分析、整合数据,而不再依赖他人拉数,节省大量沟通成本,让你...
- SQL窗口函数(sql窗口函数执行顺序)
-
背景在数据分析中,经常会遇到按某某条件来排名、并找出排名的前几名,用日常SQL的GROUPBY,ORDERBY来实现特别的麻烦,有时甚至实现不了,这个时候SQL窗口函数就能发挥巨大作用了,窗...
- sqlserver删除重复数据只保留一条,使用ROW_NUMER()与Partition By
-
1.使用场景:公司的小程序需要实现一个功能:在原有小程序上,有一个优惠券活动表。存储着活动产品数据,但因为之前没有做约束,导致数据的不唯一,这会使打开产品详情页时,可能会出现随机显示任意活动问题。...
- SQL面试经典问题(一)(sql经典面试题及答案)
-
以下是三个精心挑选的经典SQL面试问题及其详细解决方案,涵盖了数据分析、排序限制和数据清理等常见场景。这些问题旨在考察SQL的核心技能,适用于初学者到高级开发者的面试准备。每个问题均包含清晰的...
- SQL:求连续N天的登陆人员之通用解答
-
前几天发了一个微头条:...
- SQL四大排序函数神技(sql中的排序是什么语句)
-
在日常SQL开发中,排序操作无处不在。当大家需要排序时,是否只会想到ORDERBY?今天,我们就来揭秘SQL中四个强大却常被忽略的排序函数:ROW_NUMBER()、RANK()、DENSE_RAN...
- 四、mysql窗口函数之row_number()函数的使用
-
1、窗口函数之row_number()使用背景窗口函数中,排序函数rank(),dense_rank()虽说都是排序函数,但是各有用处,假如像上章节说的“同组同分”两条数据,我们不想“班级名次”出现“...
- ROW_NUMBER()函数(rownumber函数与rank区别)
-
ROW_NUMBER()是SQL中的一个窗口函数(WindowFunction)...
- Dify「模板转换」节点终极指南:动态文本生成进阶技巧(附代码)Jinja2引擎解析
-
这篇文章是关于Dify「模板转换」节点的终极指南,解析了基于Jinja2模板引擎的动态文本生成技巧,涵盖多源文本整合、知识检索结构化、动态API构建及个性化内容生成等六大应用场景,助力开发者高效利用模...
- Python 最常用的语句、函数有哪些?
-
1.#coding=utf-8①代码中有中文字符,最好在代码前面加#coding=utf-8②pycharm不加可能不会报错,但是代码最终是会放到服务器上,放到服务器上的时候运行可能会报错。③...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git 执行pull错误如何撤销 git pull fail
-
面试官:git pull是哪两个指令的组合?
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)