时间数据
时间格式是数据类型中基础也不容忽视的一类。不像整数那样大道至简也不像字符串那样包罗万象,却独有魅力,时间数据本身除了加减、比较运算外,也有下周、去年、时区等更专项的时间切换。在各类编程语言里都提供时间对象的支持,在MySQL里也有DATETIME类型。商业里的DAU、GMV、LTV也少不了时间限定和时间属性,因此数据分析时少不了对时间数据类型的处理与转换。
Python通过套件time、datetime、timeit处理时间类型数据,但面对一些情况时会不够灵活和易用,在时间序列生成和截断方面捉襟见肘,于是诞生了Arrow、Pendulum、Maya等库增强了Python的时间处理能力。本篇对4个标准库和6大第三方模块进行介绍,在面对需求时能拿到最趁手的工具。
模块概览
在Python中进行时间类型数据处理能用到的模块有:
- time:Python内置时间库,通过时间戳或元组表示时间;
- datetime:内置日期库,处理日期时间对象和属性;
- dateutil:基于datetime库的实用拓展,增强了对时间间隔和时间序列的处理;
- pd.Timestamp:pandas库用于时间处理的类;
- Arrow:优秀的Python时间库,简化了时间类型数据的解析和输出;
- Pendulum:可以和Arrow对标的时间处理库,pendulum意为钟摆;
- Delorean:在dateutil基础上进一步拓展的时间库,以《回到未来》中的时间旅行车命名;
- moment:灵感来源于Moment.js,目前相对原始;
- Maya:和Arrow等库对标,增强了对时区的处理,有调用pendulum的部分功能;
在深入这些库的使用之前,先补充一些先验知识:
epoch:时间基准点至特定时间的总秒数,一般用一个浮点数值记录,这个基准点在Unix及类Unix系统中是格林威治时间1970年01月01日00时0分0秒,因此也称为Unix时间戳(Timestamp)。因为地球是一个椭球体,当英国是中午时中国北京已经在吃晚饭了,不同经度地区的0点相对于格林威治的0点有一个时差,也就有时区(timezone)的区分,以UTC(世界协调时)作为基准,中国采用的东八区就可表示为UTC+8,对应北京时间减8个小时就是UTC时间。
基于以上需要考虑的问题,在时间类中,表示一个时间有两种基本选择:
一是用浮点数记录一个时间戳epoch,时间小于1970年则是负数,二是用元组或字典记录年月日时分秒时区等,在Python的time模块就是记录了epoch和一个元组叫struct_time,这两者之间可以互相转换。
模块特性与实践
time&datetime
time是Python内置的时间库,功能简约但实用,通常和同为内置库的datetime、pytz及calendar互相配合解决各类时间表示、计算、输出等需求。
time的常用方法有:
- time.time():得到当前时间戳Timestamp,是一个浮点数;
- time.localtime([secs]):将一个时间戳转换为当前时区的struct_time。secs参数未提供,则以当前时间为准,相当于获取当前时间now();
- time.gmtime(ts):时间戳转struct_time;struct_time是一个包含了9个元素的元组,对应着改时间对象的年月日、本年第几天等属性;
- time.mktime(t):struct_time转时间戳;
- time.strftime(“%Y-%m-%d”,t):struct_time转格式化字符串;
- time.strptime(‘2020-12-7’,”%Y-%m-%d”):字符串转struct_time;
1 | import time |
基于time模块生成的时间对象t,如果是时间戳形式表示的,是不能直接得到t是在哪一年等属性的,需要先转struct_time形式,然后就可以写st.tm_year获取所在年。st是元组,不能修改,即不能用st.tm_year=2019来修改的st的实际值。
1 | t=time.strptime('2020-12-7 13:52:15',"%Y-%m-%d %H:%M:%S") |
从文件中读取数据时常需要从字符串形式变成时间对象,就会用到strptime,是string parse time的简写,即从字符串数据类型中解析成时间类型。strftime是把时间类型格式化为字符串,是strptime的逆操作,f是format的缩写。
时间类型格式化有一套特定的占位符,下面介绍的符号在其他时间模块里也通用,因此常用的占位符还是需要心里有数才能灵活“组装”出自己需要的字符串效果的。下面表格列出了常用的时间格式化占位符,更全面的表可查阅time模块文档。
time模块常和datetime模块组合使用,time侧重在时间,datetime在日期方面方法更丰富,且datetime会和pytz及calendar配合处理时间对象。
在datetime里也有strftime和strptime,不过需要注意的是,两个库输入参数顺序的区别,datetime的strftime,格式化字符串在后,代码实例如下。
1 | from datetime import datetime |
在datetime中新建时间对象可以直接使用datetime(y, m,d,tzinfo)
输入参数,用datetime.now()
获得当前时间,通过datetime.fromtimestamp(ts)
可以将时间戳ts转为时间对象,生成的datetime时间对象在获取属性时用到的语句类似dt.year
,有year/month/day/hour/second/tzinfo等可以用。tzinfo是时区属性,datetime在时区相关处理时通常用到pytz。
1 | import pytz |
两个datetime日期相减得到的是一个时间间隔对象(imedelta),timedelta可以和数值进行乘法和整除运算,两个timedelta对象之间可以进行加减运算,但不能比较大小,datetime对象可以和timedelta对象进行加减得到新的datetime实现时间偏移。
datetime也会和内置的calendar库进行配合,顾名思义,calendar库主要用来处理和输出整年、整月的日历。
1 | print(calendar.calendar(2020)) #打印2020年日历 |
这几个库其他的实用方法有:
- time.sleep(secs):线程推迟指定的时间运行,单位为秒;
- time.asctime([t]) :把一个表示时间的元组或者struct_time表示为这种形式:’Sun Jun 20 23:21:05 1993’,如没有参数,将会将time.localtime()作为参数传入;
- time.ctime([secs]):把一个时间戳(按秒计算的浮点数)转化为time.asctime()的形式。如果参数未给或者为None的时候,将会默认time.time()为参数。它的作用相当于time.asctime(time.localtime(secs));
- calendar.leapdays(n,m):年份n到m之间的闰年数量;
dateutil
dateutil模块是基于datetime库的实用拓展,增强了对时间间隔和时间序列的处理,因此dateutil类型直接继承了datetime类型,dateutil库生成的时间对象就是datetime。Anaconda下该库已经安装,模块里有parser、easter、relativedelta、rrule等实用类进行时间处理。
1 | import dateutil #anaconda下已经安装,直接import |
dateutil的parser类用于更方便地从字符串解析为datetime对象,parser.parse(string)
可以从各种类型的字符串例如一句自然语言中解析出日期,但输入的参数string必须是字符串,输入时间戳不行(这个和下面提到的Arrow等库不同)。
因为解析为datetime类型的对象,所以可以使用datetime的各种方法和属性,例如需要知道是哪一年仍然使用dt.year
获取。
一些datetime类的方法可以基于dt实例使用,要实现从时间戳转时间对象,就可以使用dt.fromtimestamp(ts)
,获取当前时间,就可以使用dt.now()
。
1 | dt.fromtimestamp(dt.timestamp()) #时间戳与时间对象互转 |
dateutil计算时间间隔的方法封装在relativedelta里,通过输入参数months等明确间隔的时间距离,tz用于处理时区。
1 | dt+dateutil.relativedelta.relativedelta(months=1, weeks=1) |
rrule类用于生成和处理一个时间序列。rrule的主要参数有:
- freq:声明序列重复的周期;
- count:生成多少个时间对象;
- dtstart:开始的时间点;以上例子生成的是一个由4个时间对象组成的序列,开始时间是2020年12月7号,每月重复一条记录。rrule.rrulestr()是把字符串输入当参数。
1
2
3
4
5
6
7list(dateutil.rrule.rrule(freq=dateutil.rrule.MONTHLY, count=4, dtstart=datetime(2020, 12,7)))
# [datetime.datetime(2020, 12, 7, 0, 0),datetime.datetime(2021, 1, 7, 0, 0),...]
list(dateutil.rrule.rrulestr("""
DTSTART:20201207T090000
RRULE:FREQ=DAILY;INTERVAL=10;COUNT=4
"""))
#效果同上,rrulestr是根据字符串规则生成时间序列
pandas
实际在进行数据分析时,通常都会用到pandas库却不一定会导入datetime等库,而pandas模块也提供了Timestamp、Timedelta等类用于时间类型数据的处理转换。直接使用pd.Timestamp也更容易进行广播运算。
pandas的Timestamp对象用法和datetime库基本一致,各种dt.year
属性都有,也有dt.isleapyear
用于判断是否是闰年。pd.Timedelta对应datetime的timedelta,表示时间间隔。
1 | df['时间']=pd.to_datetime(df['dt']) |
前文《用pandas处理时间格式数据》讲述了一个处理Excel文件中时间数据的案例。
Arrow
Arrow是一个优秀的Python时间处理库,现在其他有追求的第三方时间处理库都喜欢在文档里对标Arrow,足矣见Arrow的影响力。Arrow通过收束接口增强了其易用性,可以快速上手使用,get统筹各种输入的解析,replace负责各种时间要素的修改,format解决各类格式化输出的需求,range处理时间序列生成问题。
Arrow解析字符串或datetime对象得到的是一个自定义时间对象,通过dt.time、dt.datetime、dt.timestamp等将时间数据从Arrow内置对象转为time等库的时间对象,一些例子如下。
1 | import arrow #在Anaconda下已经安装 |
Arrow的具体用法可参考前文《Python处理时间数据的另一种选择,在标准库之外|Arrow使用笔记》。
Pendulum
Pendulum也是一款很优秀的Python时间处理模块,其内置数据类型拓展自datetime,与datetime有着很好的兼容性。Pendulum比dateutil功能更丰富,足矣和Arrow对标。Arrow的易用性体现在接口简洁,Pendulum的易用性表现在很多datetime的方法都兼容,而且Pendulum的文档页面也更美观漂亮。Pendulum[ˈpendʒələm]意为钟摆,是很好的时间意向。Pendulum通过其内置的DateTime对象实现和拓展datetime.datetime的功能,同时封装出Duration、Period及Timezones处理时间偏移、时区、时间序列。
1 | import pendulum |
其他的一些实用方法如下:
- pendulum.datetime(2020,5,7):输入年月日等生成DateTime,对应着datetime.datetime()的写法;
- pendulum.today():获取当天时间, .tomorrow() .yesterday() 等可以用;
- pendulum.local(args):获取当地时间的对象,可以输入年月日等;
- pendulum.parse(text):从文本中解析出时间对象,有个类似的方法是pendulum.from_format(text,s);
- pendulum.from_timestamp(ts):把时间戳ts转为时间对象;
- dt.int_timestamp:把dt表示为整数的timestamp,对应的还有.float_timestamp;
- pendulum.timezone(“Europe/Paris”):生成一个时区对象;
- d2.diff_for_humans(dt):将时间间隔按自然语言输出;
Pendulum的一些函数需要输入DateTime作为参数时,输入datetime对象也兼容,例如Period时期对象的start、end对象输入DateTime对象或datetime对象都可以,更详细的Pendulum特性可阅读《挑战Arrow,需要怎样的实力?Pendulum使用笔记》。
Delorean
dateutil库在datetime库基础上进行拓展,Delorean站在dateutil的肩膀上进一步增强了时间处理能力,其接口更偏向面向对象的写法,时间戳使用epoch定义,其时间对象和datetime对象兼容性也很高,并且内置时间对象可以直接和datetime.timedelta进行运算。
Delorean是《回到未来》中的主角的时间旅行车,作为一个以epoch表示时间的程序库挺契合的。
Delorean抽象了多个接口用于解析和转换其他格式数据为时间对象,解析字符串用parse、处理时间戳用epoch、输入的是datetime对象直接用Delorean()。获取对象的年月日等属性,需转datetime再使用datetime的接口。
1 | from delorean import Delorean |
Delorean修改时间要素是用replace,而改时区是使用的shift。除了用stops生成时间序列外,还有range_daily()、range_hourly()等快速方法生成每天或每小时的时间序列。Delorean和datetime的协作很方便,但接口不够简洁和成体系,获取属性还需要转为datetime,显得常用的功能却没有优先封装,与Arrow、Pendulum等库还有些差距,是一个值得了解的Python时间库,详细了解其用法可看前文《设定基准点去时间旅行|Delorean使用笔记》。
moment
和Arrow类似,moment也是灵感来自Moment.js库。moment是一个在发展中的库,基本功能不缺,但也不是很完善,其文档 建议优先考虑Arrow及Pendulum库。
moment将数据的输入封装在moment.date里,在解析能力上,比Arrow的get更进一步,例如get传入tomorrow或者2 weeks ago是会报错的,这是arrow的get还不支持的写法,但moment.date可以解析。
1 | import moment |
moment的时间对象也是自定义的对象,获取其属性使用dt.year
的写法,和其他库一致,进行时间偏移用的add和subtract方法,同时也有replace的接口,而且写dt.replace(day=2)
或者dt.replace(days=2)
都没出问题。输出格式化的字符串使用format。通过dt.datetime
转为dateime类型,而输出时间戳是用dt.epoch()
方法。
1 | dt=moment.now() #还有utcnow()可以用 |
moment目前的接口还是偏少,生成一个时间序列目前还不能实现。
使用moment时,一个小问题是用pip install moment
可能会安装不上,需要通过pip install moment --user
去安装。
Maya
Maya站在datetime、pendulum、snaptime等模块的肩膀上发展有一定特色的时间处理能力,Maya自定义对象MayaDT也是通过epoch定义时间,能很好地避免一些时区问题。
Maya的时间创建能力上排名前列,有丰富的接口用于从各种数据中解析出时间对象,when和parse可以从一些自然语言字符串中解析出时间要素,这方面和moment不遑多让,例如写maya.when('tomorrow')
和.when('2 weeks ago')
等;当然从time/datetime对象、时间戳转Maya对象也是没有压力。
1 | import maya |
在输出和转换方面,有dt.datetime()
方法将MayaDT对象转为datetime对象,也能直接通过dt.year
获取MayaDT对象的属性,有dt.iso8601()
输出满足ISO-8601标准的时间字符串,和from_iso8601相对应。几个优秀库都有的输出为自然语言功能在Maya里封装为dt.slang_time()
,并且还有slang_date也能使用,slang是俚语的意思。
1 | dt=maya.when('2020, 12, 7') |
Maya的很多方法调用了其他时间库,例如dt.year等属性用了datetime库、snap方法是调用了snaptime库、parse和add用到了Pendulum库,很多需求Maya没有自己去造轮子,同时也显得依赖项有些多,要深入了解Maya的用法可以翻看前文《博采众长穿梭时空|Maya库使用笔记》。
总结
在数据处理和数据分析过程中,主要需要解决的数据需求有以下几点:
- 生成时间对象,从字符串或者写赋值语句得到一个时间对象;从内置的time/datetime对象转更容易处理的时间对象,如数据列是从Excel读入的,去解析该列为时间对象;
- 对特定时间对象t,获取年月日、分钟等时间要素;
- 时间运算;
- 时间间隔Timedelta,两个时间对象相减;
- 一个时间对象+一个差值后得到新的时间对象,例如获取t一周后的时间t2,
- 时间对象转为特定格式的字符串;
- 时间序列的整体移动与抽样;
- 非结构日期处理,从自然语言中解析时间;
各个库解决该需求的方式总结如下表。
这几个库的使用笔记ipynb文件及xmind文件可在公众号后台回复 time 获取。