第5章 Pandas文本与日期 5.1字符串处理 5.1.1字符串处理流程 在Python数据分析过程中,经常要对字符串数据进行处理,例如对字符串的删除、拆分、组合、查找、匹配、替换、计数等应用,这其中的很多应用具备正则表达式的功能。当相关字符串方法已知可用正则表达式时它的默认参数为regex=True,可用regex=False关掉正则表达式。Pandas中字符串方法的整体使用流程及说明如图51所示。 图51Pandas中字符串方法的整体使用流程及说明 在Pandas中主要是以Series为单位来处理字符串的,它的语法为Series.str.str方法()。例如Series.str.split(),代表的是Pandas中以某列为单位进行字符串拆分。在Series中,直接对列用split()来分列是不允许的(系统会报错AttributeError: 'Series' object has no attribute 'Split'),但如果先用.str将这一列转换为类似字符串的格式,则不会有问题。假如要将这一列转换为数值列,直接对数值列进行str的相关操作也会报错,必须先执行astype(str),然后进行对应的字符串操作,这样就不会有问题了,即Series.astype(str).str.str方法()。 在Series.str的str方法中,str是转换器。在Pandas中,有3个功能强大的转换器(或称访问器): str、dt、cat。str转换器用于处理字符串对象,后面常用于连接字符串方法; dt转换器用于处理时间对象,后面常用于连接时间属性; cat转换器用于处理分类对象的数据。在Pandas中,除replace()方法以DataFrame及Series为处理对象外,其他的字符串处理方法一般以Series为处理对象。 str转换器相关方法见表51。 表51str转换器相关方法 方法语 法 说 明作用 str.isdigit()是否只由数字组成判断是否 str.isdecimal()是否只包含十进制字符判断是否 str.isnumeric()是否只由数字组成判断是否 str.isalnum()是否由字母和数字组成判断是否 str.isalpha()字符串至少包含一个字符且所有字符都是字母(汉字)判断是否 str.islower()至少包含一个小写字母,且不包含大写字母判断是否 str.isupper()至少包含一个大写字母,且不包含小写字母判断是否 str.istitle()所有单词以大写字母开头,其余小写判断是否 str.isspace()只包含空白符判断是否 str.startswith()以某指定的字符或字符串开头判断是否 str.endswith()以某指定的字符或字符串结尾判断是否 str.get._dummies用于数据的离散特征取值,返回的值为0或1数值计算 str.len()计算字符串中每个元素的长度数值计算 str.index()计算字符串首次出现的索引位置位置计算 str.rindex()计算字符串最后一次出现的位置位置 str.find()找到字符串首次出现的索引位置,如果未找到,则返回-1位置 str.rfind()找到字符串首次出现的索引位置,如果未找到,则返回-1位置 str.get()从字符串中提取元素提取 str.strip()删除字符串左右两边的空白字符(含换行符)剪切 str.lstrip()删除字符串左边的空白字符(含换行符)剪切 str.rstrip()删除字符串右边的空白字符(含换行符)剪切 str.slice()按下标截取字符串剪切 str.slice_replace()按下标替换剪切 str.removeprefix()删除字符串中的前缀剪切 str.removesuffix()删除字符串中的后缀剪切 str.repeat()按指定的次数重复字符串重复 str.partition()在分隔符第1次出现的地方拆分字符串拆分 str.rpartition()在分隔符最后出现的地方拆分字符串拆分 str.join()以指定的字符串为分隔符并生成一个新的字符串拼接 str.cat()合并多列的字符串拼接 str.capitalize()首字母大写转换 str.title()(有分隔符分隔时)字符串内所有单词的首字母大写转换 str.lower()字母全部小写转换 str.upper()字母全部大写转换 str.swapcase()大小写互换转换 str.swap()按指定的行宽换行字符串转换 str.casefold()将指定的字符串转换为大小写折叠转换 str.translate()通过给定的映射表映射字符串中的所有字符转换 str.normalize()返回字符串的Unicode标准形式转换 str.pad()字符串的左右补齐填充 str.center()字符串居中填充填充 str.ljust()字符串左对齐填充填充 str.rjust()字符串右对齐填充填充 str.zfill()在字符串前面填充填充 str.repeat()对字符串指定重复的次数填充 str.warp()在指定的位置加回车符填充 str.decode()使用指定的编码解码字符串解码 str.encode()使用指定的编码对字符串进行编码编码 以str.cat()的应用为例。Pandas中的str.cat()的用法如下: Series.str.cat(others=None, sep=None, na_rep=None, join='left') 图52cat()方法的常见用法 参数说明: others参数为Series、Index、DataFrame、np.ndarray或listlike; sep参数为str,默认值为' '。na_rep参数为str或None,默认值为None。join参数为'left'、'right'、'outer'、'inner',拼接方式的默认值为left。 str.cat()中others参数的常见用法,如图52所示。 以拼接多个列为例,代码如下: #ch05d001.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\文本处理.xlsx', sheet_name=1 ).head(3) df['合并列']=df['运单编号'].str.cat([df['城市'],df['楼牌号']],sep='、') df.iloc[:,[0,4]] 返回的值如下: 运单编号合并列 0YD001YD001、北京、AA02幢02楼201 1YD003YD003、上海、aA03幢03楼301 2YD006YD006、广州、Aa04幢04楼401 以拼接DataFrame为例,代码如下: df1= df.iloc[:,[0,4]] df['合并列1']=df['字符标识'].str.cat(df1,sep='、') df.iloc[:,[0,5]] 返回的值如下: 运单编号合并列1 0YD001ABC123、YD001、YD001、北京、AA02幢02楼201 1YD003b1A32C、YD003、YD003、上海、aA03幢03楼301 2YD0061c2a3abb、YD006、YD006、广州、Aa04幢04楼401 5.1.2正则表达式 正则是Regular Expression(正则表达式)的简写,正则表达式通过一些特定的元字符实现强大、便捷与高效的文本匹配、查找、替换等功能,因此,正则表达式已经成为所有主流编程语言的必备项。很值得去认真学习与了解。 正则表达式由“元字符”和其他“普通文本字符”两部分组成,其中,正则表达式中的“元字符”主要分为基本元字符、数字元字符、位置元字符、特殊元字符等。 1. 基本元字符 基本元字符及其语法说明见表52。 表52基本元字符及语法说明 元字符语 法 说 明 .(除换行符以外的)任意字符 |逻辑或 []字符集合中的任一字符 [^]不是字符集合中的任一字符 -区间定义 \\转义符 ()生成子表达式 2. 数字元字符 常见的数字元字符及其语法说明见表53。 表53数字元字符及其语法说明 元字符语 法 说 明 *零次或多次(贪婪模式) *?*的懒惰模式 +一次或多次(贪婪模式) +?+的懒惰模式 ?前一字符的零次或一次 {n}n次重复 {m,n}重复m到n次 {n,}重复n次到更多次 {n,}?{n,} 的懒惰模式 3. 特殊元字符 特殊元字符及其语法说明见表54。 表54特殊元字符及其语法说明 元字符语 法 说 明 \\d任意数字,等价于[09] \\D不是数字,等价于[^09] \\s空白字符,等价于[\\n\\r\\t\\v] \\S不是空白字符,等价于[^\\n\\r\\t\\v] \\w任意字母、数字或下画线,等价于[azAZ09_] \\W不是任意字母、数字或下画线,等价于[^azAZ09_] \\f换页符 \\n换行符 \\r回车符 \\t制表符 4. 位置元字符 位置元字符及其语法说明见表55。 表55位置元字符及其语法说明 元字符语 法 说 明 ^开始 $结束 \\A字符串的开头(忽略re.M) \\Z字符串的结尾(忽略re.M) \\b单词的边界 \\B不是\\b 5. 追溯与查找 各类正则断言均属于分组不捕获,匹配的结果为(零宽度的)位置,其作用是给指定位置添加限定的条件,如图53所示。 图53正则断言 追溯与查找元字符及相关语法说明见表56。 表56追溯与查找元字符及相关语法说明 元字符语 法 说 明 ?=名称: 正向先行断言(正前瞻) 语法: (?=pattern) 作用: 匹配pattern表达式前面的内容,不返回本身 举例: a(?=b),先行断言,a 只有在 b 前面才匹配 ?!名称: 负向先行断言(负前瞻) 语法: (?!pattern) 作用: 匹配非 pattern 表达式前面的内容,不返回本身 举例: a(?!b),先行否定断言,a 只有不在 b 前面才匹配 ?<=名称: 正向后行断言(正后顾) 语法: (?<=pattern) 作用: 匹配pattern表达式后面的内容,不返回本身 举例: (?<=b)a,后行断言,a 只有在 b 后面才匹配 ?正则表达式)”。应用举例,代码如下: df['收货地址'].str.extract('(?P<幢数>\\d+)(?P<幢>\\D+)') 返回的值如下: 幢数幢 002幢 103幢 204幢 采用命名分组形式,提取收货地址中的幢、楼信息; 未被命名的分组会以数值的形式显示,代码如下: df['收货地址'].str.extract('(?P<幢数>\\d+)(?P<幢>\\D+)(\\d+)(\\D+)(\\d+)') 返回的值如下: 幢数幢234 002幢02楼201 103幢03楼301 204幢04楼401 7. 提取全部 Pandas中的str.extractall()的用法如下: Series.str.extractall(pat, flags=0) str.extract()方法用于向列方向扩展。str.extractall()方法用于向行方向扩展。应用举例如下: df['收货地址'].str.extractall('(?P<数字>\\d+)(?P<文本>\\D+)') 返回的值如下: 数字 文本 match 0 002幢 102楼 1 003幢 103楼 2 004幢 104楼 8. 替换 Pandas中的str.replace()的用法如下: Series.str.replace(pat, repl, n=- 1, case=None, flags=0, regex=None) str.replace()方法用于替换字符串中出现的每个模式或正则表达式,等价于str.replace()或re.sub()。 应用举例如下: df['收货地址'].str.replace('路\\w{5}','一线城市') 返回的值如下: 0北京一线城市02楼201 1上海一线城市03楼301 2广州一线城市04楼401 Name:收货地址, dtype: object 5.2日期和时间 Pandas继承了NumPy库和datetime库的时间相关模块,提供了Timestamp、Period、 Timedelta、DatetimeIndex、PeriodIndex、TimedeltaIndex这6种时间相关的类,使其能更高效地处理时间序列数据。Pandas支持时区,使用的是datetime64[ns]数据类型,可以精确到毫秒、纳秒,能够轻松处理金融等对时间精度有要求的行业。 在这6个类中,Timestamp是Pandas中最基础的,也是最常用的时间序列类型,它是以时间戳为索引的Series。当创建一个带有DatetimeIndex的Series时,Pandas就会知道对象是一个时间序列。 Pandas中这6个时间序列类型的数据结构与数据类型的对照见表57。 表57Pandas时间序列数据结构 概念标量类数 据 类 型索引创 建 方 法 时间点Timestampdatetime64[ns], datetime64[ns,tz]DatetimeIndexpd.to_datetime、 pd.date_range 时间段Periodperiod[freq]PeriodIndexdf.to_period、 pd.period_range 时间差TimedeltaTimedelta64[ns]TimedeltaIndexpd.to_timedelta、 pd.timedelta_range 时间偏移DateOffsetNoneNonepd.DateOffset 以下是表57的内容说明。 (1) 时间点(Datetime),也可以称为时间戳(Timestamp)。一系列的时间戳构成了DatetimeIndex。在Series中这些数据的类型为datetime64[ns],如果存在时区设置,则在Series中这些数据的类型为datetime64[ns,tz]。 (2) 持续的时间段(Period)都由start和end两部分组成。一系列的时间段构成了PeriodIndex。 (3) 两个时间点的差值代表的是时间差(Timedelta),代表的是某事件的持续时间。一系列的时间戳构成了TimedeltaIndex。 (4) 如果需要在某一日期的基础上进行未知日期的计算,则可以采用日期偏移(DateOffset)。 Pandas中时序分析常用的流程如图54所示。 图54Pandas中时序分析常用的流程 5.2.1时间点 1. 时间戳 在Pandas中,可用pd.Timestamp()进行解析、转换、创建单个时间戳。以pd.Timestamp为例,代码如下: #ch05d006.ipynb import pandas as pd pd.Timestamp(2022,12,13) #pd.Timestamp('2022/12/13') #pd.Timestamp(year=2022,month=12,day=13) #pd.Timestamp(1670889600,unit='s') 一个完整的时间戳是由年、月、日、时、分、秒等组成,返回的值如下: Timestamp('2022-12-13 00:00:00') 利用pd.Timestamp()获取当前时间点,代码如下: pd.Timestamp('now') pd.Timestamp('today') 利用pd.Timestamp()获取当日日期、当前时间及星期。应用举例,代码如下: pd.Timestamp('now').date() pd.Timestamp('today').time() pd.Timestamp('today').day_name() 2. 创建日期时间 在Pandas中,可用pd.to_datetime()创建日期时间。pd.to_datetime的第1个参数可为str、int、float、datetime、list、1d array、Series、DataFrame、dictlike、tuple等。以列表中的字符串全部转换为日期时间为例,代码如下: pd.to_datetime(['2022-12-13','12/13/2022','2022.12.13','13/12/2022','Dec 13, 2022']) 列表中存在多个时间戳值,从而构成了一个DatetimeIndex,数据类型为datetime64[ns],freq为None,返回的值如下: DatetimeIndex(['2022-12-13', '2022-12-13', '2022-12-13', '2022-12-13', '2022-12-13'], dtype='datetime64[ns]', freq=None) 3. 日期范围 pd.date_range()、pd.bdate_range()用于生成指定长度的DatetimeIndex; 二者的区别在于是否包含周六、周日。4个主要参数如表58所示。 表58date_range的参数说明 参数参 数 说 明 Start开始日期,可省 End结束日期,可省 Periods固定时期,取值为整数或None Freq日期偏移量(频率),取值为string或DateOffset 利用pd.date_range()函数创建5个日期列,代码如下: #ch05d007.ipynb import pandas as pd df = pd.DataFrame( { 'Date1':pd.date_range(start = '2022-12-12',periods=3), 'Date2':pd.date_range(end = '2022-12-15',periods=3, freq='D'), 'Date3':pd.date_range(end = '2022-12-15',periods=3, freq='A'), 'Date4':pd.date_range(end = '2022-12-15',periods=3, freq='QS'), 'Date5':pd.date_range(end = '2022-12-15',periods=3, freq='MS'), } ) df pd.date_range()类似于Power Query中List.Dates()的功能,用于生成一个给定开始时间和持续时长的日期列表。返回的值如下: Date1Date2Date3Date4Date5 0 2022-12-122022-12-13 2019-12-31 2022-04-01 2022-10-01 1 2022-12-132022-12-14 2020-12-31 2022-07-01 2022-11-01 2 2022-12-142022-12-15 2021-12-31 2022-10-01 2022-12-01 利用dtypes属性查看各列的数据类型,代码如下: df.dtypes 返回的值如下: Date1datetime64[ns] Date2datetime64[ns] Date3datetime64[ns] Date4datetime64[ns] Date5datetime64[ns] dtype: object 在DataFrame中利用pd.date_range()创建DatetimeIndex,代码如下: #ch05d008.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','产品','订单数'], index_col='接单时间' ).head(3) pd.date_range(df.index.min(),df.index.max(),freq='4M') 返回的freq为'4M',返回的值如下: DatetimeIndex(['2020-04-30', '2020-08-31', '2020-12-31', '2021-04-30', '2021-08-31'],dtype='datetime64[ns]', freq='4M') 在以上代码中freq参数的对照表见表59。 表59时间序列频率对照表 别名英 文 描 述中 文 描 述 D/BDay/BusinessDay日历日的每天/工作日的每天 H/T(或Min)/SHour/Minute/Second时/分/秒 M/BMMonthEnd/BusinessMonthEnd日历日的月末/工作日的月末 MS/BMSMonthStart/BusinessMonthStart日历日的月初/工作日的月初 WMONWeekMonday每周从星期一开始计算,其他的有WTUE…… WOM1MONWeekOfMonth在本月的第1周创建按周分隔的日期,例如WOM3FRI代表每月的第3个星期五 QJAN/BQJANQuarterEnd/BusinessQuarterEndJAN表示月份结束的季度,也可以是FEB…… QSJAN/QBSJANQuarterStart/ BusinessQuarterStartJAN表示月份结束的季度,也可以是FEB…… AJAN/BAJANBusinessYearEnd/YearStartJAN表示月份结束的季度,也可以是FEB…… ASJAN/BASJANYearStart/BusinessYearStartJAN表示月份结束的季度,也可以是FEB…… 表59中的各类频率组合,可用图55加深理解与记忆。例如A/Q/M单字母时代表的是其对应频率的End,加上s时则为其对应频率的start。 图55时间频率组合规律说明 为加深理解,仍以202212为例,表510是M、BM、MS、BMS的对照说明。 表510时间序列频率对照表 别名代表的频率(202212)举例 M月末20221231 BM月末的工作日20221230 MS月初2022121 BMS月初工作日2022121 4. 解析日期 在Pandas中,在pd.read_excel()读取过程中,可通过parse_dates手动设置或系统自动解析,将对应的文本型日期时间数据解析为日期时间数据; 甚至可以将导入DataFrame的日期时间数据设置为索引列,代码如下: #ch05d009.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','产品','订单数'], #parse_dates=True, index_col='接单时间' ).head(3) df.index 生成DatetimeIndex,dtype为datetime64[ns],freq为None。如果以上代码返回的数据类型为Object,则可以启用parse_dates=True进行日期解析,返回的值如下: DatetimeIndex(['2020-04-28', '2020-05-31', '2020-06-30'], dtype='datetime64[ns]', name='接单时间', freq=None) 将数据导入DataFrame时,日期时间数据会被自动解析为datetime64[ns],代码如下: df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', #parse_dates=True, usecols=['接单时间','产品','订单数'],) df.dtypes 查看数据类型,返回的值如下: 接单时间datetime64[ns] 产品object 订单数int64 dtype: object 将接单时间列设置为索引列并查看索引列,代码如下: df1 = df.set_index(df['接单时间']) df1.head(3).index DatetimeIndex的freq为None,返回的值如下: DatetimeIndex(['2020-04-28', '2020-05-31', '2020-06-30'], dtype='datetime64[ns]', name='接单时间', freq=None) 在Pandas中,可使用dt转换器访问datetime对象并对year、month、day、hour、minute、second、quarter等属性进行访问,见表511。 表511Pandas时间戳属性 细分内容 常用year、month、day、hour、minute、second、value 偶用asm8、day_of_week、day_of_year、dayofweek、dayofyear、days_in_month、daysinmonth、freq、fold、freqstr、is_leap_year、is_month_end、is_month_start、is_quarter_end、is_quarter_start、is_year_end、is_year_start、microsecond、nanosecond、quarter、tz、week、weekofyear、start_time、end_time 以is开头的属性均为判断是否满足条件,返回的值为True或False。 在Pandas中,可使用dt转换器访问datetime对象并对其显示格式进行设置。Python中datetime的常见格式说明见表512。 表512datetime的常见格式说明 类型格 式 说 明 %F%Y%m%d的简写,解析到纳秒 %D%m%d%y的简写 %Y四位的年份 %y两位的年份 %m两位的月份 %d两位的日期 %H24h制的小时 %I12h制的小时 %M两位的分钟 %S秒 %w星期几(星期天为0) %W一年中第几周(星期一为每周的第一天) %U一年中第几周(星期天为每周的第一天) %z以+HHMM或-HHMM的UTC时区偏移,如果没有时区,则为空 (1) 利用dt转换器进行属性访问。利用assign()方法创建连续的多列,代码如下: df = df.assign( 年月=df.接单时间.dt.strftime('%Y-%m'), 年=df.接单时间.dt.year, 季=df.接单时间.dt.quarter, 月=df.接单时间.dt.month ).head(3) df 当需解析的时间数据因某些原因无法正确解析时,可采用strftime()的参数进行强制格式转换,返回的值如下: 接单时间产品订单数年月年季月 0 2020-04-28蛋糕纸22020-04202024 1 2020-05-31蛋糕纸22020-05202025 2 2020-06-30苹果醋22020-06202026 (2) 利用dt转换器的连接方法。继续创建多列,代码如下: df = df.assign( 月名=df.接单时间.dt.month_name(), 星期=df.接单时间.dt.day_name()) df 返回的值如下: 接单时间产品订单数年月年季月月名星期 0 2020-04-28蛋糕纸22020-04202024AprilTuesday 1 2020-05-31蛋糕纸22020-05202025MaySunday 2 2020-06-30苹果醋22020-06202026JuneTuesday 5. 日期时间索引 DatetimeIndex具备以下优点: ①可以通过切片方式快速索引; ②可以通过索引的属性快速访问数据; ③同频率数据的快速合并等。 (1) 当创建一个带有DatetimeIndex的Series时,Pandas就会知道对象是一个时间序列,可以进行与索引相关操作,代码如下: #ch05d010.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','产品','订单数'], #parse_dates=True, ) df = df.set_index(pd.to_datetime(df['接单时间'])) #df = df.set_index(df['接单时间']) df.sort_index().loc['2020'].count() 需要提醒的是: 为了避免索引列中数据可能存在的乱序情形,在索引与切片之前先对DatetimeIndex进行索引排序,返回的值如下: 接单时间13 产品13 订单数13 dtype: int64 也可以对具体的年月进行访问,代码如下: df = df.set_index(pd.to_datetime(df['接单时间'])) #df = df.set_index(df['接单时间']) df.sort_index().loc['2020-10'] 返回的值如下: 接单时间产品订单数 接单时间 2020-10-02 2020-10-02油漆9 2020-10-04 2020-10-04包装绳7 2020-10-04 2020-10-04钢化膜11 或对具体的日期进行访问,代码如下: df = df.set_index(pd.to_datetime(df['接单时间'])) #df = df.set_index(df['接单时间']) df.loc['2020-10-4'] 返回的值如下: 接单时间产品订单数 接单时间 2020-10-04 2020-10-04包装绳7 2020-10-04 2020-10-04钢化膜11 对第4季的数据进行索引,代码如下: df.loc['2020Q4'] 返回的值如下: 接单时间产品订单数 接单时间 2020-10-02 2020-10-02油漆9 2020-10-04 2020-10-04包装绳7 2020-10-04 2020-10-04钢化膜11 (2) 当不存在时间索引列时,也可以利用dt转换器进行访问,代码如下: df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','订单数','入库数'], parse_dates=True, ) df.loc[(df.接单时间.dt.year==2020) & (df.接单时间.dt.quarter==4) ] 以上代码返回的值如下: 接单时间订单数入库数 10 2020-10-0299 11 2020-10-0477 12 2020-10-041111 5.2.2时间段 1. 期间 pd.Period()用于创建一个具体的时段,代码如下: pd.Period(2021,freq='A-Feb') 返回的值如下: Period('2021', 'A-FEB') 以Q为频率,代码如下: pd.Period('2022-12-13', freq = "Q") 返回的值如下: Period('2022Q4', 'Q-DEC') 2. 创建期间 在Pandas中有Series.to_period()和DataFrame.to_period()两种常见用法,s.to_period()或df.to_period()用于描述该日期处于哪个时期。Series的主要参数为freq,DataFrame中还有axis参数。s.to_period()用法的应用举例,代码如下: A = pd.date_range('2022-12-12',periods=3,freq='3M') A.to_period() 返回的值如下: PeriodIndex(['2022-12', '2023-03', '2023-06'], dtype='period[3M]') df.to_period()用法的应用举例,以时间段为分组计算依据,代码如下: df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','订单数','入库数'], parse_dates=True, index_col='接单时间' ) df.to_period('Q').head(3) 返回的值如下: 订单数入库数 接单时间 2020Q222 2020Q222 2020Q222 利用索引及其属性作为分组的依据,然后计算订单与入库数,代码如下: df.groupby(df.index.year).sum() 或者利用to_period()的频率(频率为年的简码为A), 代码如下: df.to_period('A').reset_index().groupby('接单时间').sum() 返回的值如下: 订单数入库数 接单时间 20206157 2021 4140 3. 期间范围 pd.period_range(start=None, end=None, periods=None, freq=None,name=None)用于创建固定频率的PeriodIndex。创建一个Period_range,代码如下: A = pd.period_range('2022/7/1',periods=3,freq='M') A 生成PeriodIndex,返回的值如下: PeriodIndex(['2022-07', '2022-08', '2022-09'], dtype='period[M]') 与DatetimeIndex的属性类似,在PeriodIndex中,它也有year、quarter、month、day、hour、 minute、 second、 weekday、weekofyear、dayofyear等属性。以year属性为例,代码如下: A.year 返回的值如下: Int64Index([2020, 2020, 2020], dtype='int64') 以dayofweek属性为例,代码如下: A.dayofweek 返回的值如下: Int64Index([4, 0, 2], dtype='int64') 以end_time属性为例,代码如下: A.end_time 返回的值如下: DatetimeIndex(['2020-07-31 23:59:59.999999999', '2020-08-31 23:59:59.999999999', '2020-09-30 23:59:59.999999999'], dtype='datetime64[ns]', freq=None) 4. 期间索引 当创建一个带有DatetimeIndex的Series时,可以进行与索引相关操作,代码如下: #ch05d011.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','产品','订单数'], index_col='接单时间' ).to_period('Q') df.head(3).index 返回的值如下: PeriodIndex(['2020Q2', '2020Q2', '2020Q2'], dtype='period[Q-DEC]', name='接单时间') 对2020Q4值进行筛选,表达式如下: df.loc['2020Q4'] 返回的值如下: 产品订单数 接单时间 2020Q4油漆9 2020Q4包装绳7 2020Q4钢化膜11 5.2.3时间差 1. 时间差 Timedelta用于创建或计算时间差,常用的时间单位有周、日、时、分、秒等。导入数据,代码如下: #ch05d012.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['入库日期',],nrows=3) df['时差'] = df['入库日期']- df['入库日期'].min() df 返回的值如下: 入库日期时差 0 2020-05-26 13:54:240 days 00:00:00 1 2020-06-03 06:49:227 days 16:54:58 2 2020-07-03 00:03:21 37 days 10:08:57 查看数据类型,代码如下: df.dtypes 返回的值如下: 入库日期datetime64[ns] 时差timedelta64[ns] dtype: object pd.Timedelta()的参数有多种传递方式,利用assign()创建多列,代码如下: df = df.assign( 时差1=pd.Timestamp(2022,12,13)- pd.to_datetime(df['入库日期']), 时差2=pd.to_datetime(df['入库日期'])-pd.Timedelta('3 days 3 hours 3 minutes'), 时差3=pd.to_datetime('2022.12.13')-pd.Timedelta(5,unit='d'), 时差4=pd.to_datetime('2022.12.13')-pd.Timedelta(days=5), ) df Timedelta时间差功能与用法类似于Power Query中的Duration。支持的运算有与标量值相乘、与时间戳加减,以及时间差之间的加、减、乘、除。返回的值如图56所示。 图56时间差 在以上代码中时间差freq参数的常用单位见表513。 表513时间差频率对照表 频率代码 周W、w、weeks、week 天D、d、days、day 时H、h、hours、hour 分T、m、minutes、minute 秒S、s、seconds、second 毫秒I、milli、millis、millisecond、milliseconds 微秒U、micro、micros、microsecond、microseconds 纳秒N、ns、nano、nanos、nanosecond、nanoseconds 毫秒、微秒、纳秒等高精度单位在金融等行业应用较多,这样的精度是Excel所不具备的。 查看DataFrame中各列的数据类型,代码如下: df.dtypes 返回的值如下: 入库日期datetime64[ns] 时差timedelta64[ns] 时差1timedelta64[ns] 时差2datetime64[ns] 时差3datetime64[ns] 时差4datetime64[ns] dtype: object 2. 时间差范围 pandas.timedelta_range(start=None, end=None, periods=None, freq=None, name=None, closed=None),返回固定频率的TimedeltaIndex,默认为天数。应用举例如下: A = pd.timedelta_range(start='1 day', end='2 days', freq='16H') A freq为16H,返回的值如下: TimedeltaIndex(['1 days 00:00:00', '1 days 16:00:00'], dtype='timedelta64[ns]', freq='16H') 3. 时间差索引 对"时差1"列中的信息进行提取,代码如下: df['相差天数']=pd.TimedeltaIndex(df['时差1']).days df['间隔天数']=df['时差1'].dt.days df.iloc[:,[0,2,6,7]] 常用的时间差属性有days、seconds等,返回的值如下: 接单时间时差1相差天数间隔天数 0 2020-04-28 28 days 13:54:242828 1 2020-05-313 days 06:49:2233 2 2020-06-303 days 00:03:2133 5.2.4时间偏移 1. 日期偏移 pd.DateOffset()所实现的功能类似于pd.Timedelta(),但二者实现的方式有所不同。应用举例如下: #ch05d013.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','入库日期'],nrows=3) df['后移一天'] = df['接单时间'] + pd.DateOffset(days=1) df['后移二月'] = df['接单时间'] + pd.DateOffset(months=2) df.head(3) 返回的值如下: 接单时间入库日期后移一天后移二月 0 2020-04-28 2020-05-26 13:54:24 2020-04-29 2020-06-28 1 2020-05-31 2020-06-03 06:49:22 2020-06-01 2020-07-31 2 2020-06-30 2020-07-03 00:03:21 2020-07-01 2020-08-30 2. 偏移 pd.offsets后面可接的方法较为繁多。简单应用举例,代码如下: df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['入库日期'],nrows=3) df['后移一周'] = df['入库日期'] + pd.offsets.Week() df['后移三天'] = df['入库日期'] + pd.offsets.Day(3) df['后移五时'] = df['入库日期'] + pd.offsets.Hour(5) df['后移一刻'] = df['入库日期'] + pd.offsets.Minute(15) df 返回的值如图57所示。 图57返回的值 5.2.5频率转换 1. 降采样 降采样(resample)用于将高频率数据聚合到低频率,采样的前提是index必须为时间序列。在降采样中,目标频率必须是源频率的子时期(subperiod)。以下创建的DataFrame用于降采样及升采样,代码如下: #ch05d014.ipynb import pandas as pd df = pd.DataFrame( data = {"Num":range(1,7)}, index=pd.date_range('2022/12/9',periods=6, freq='2d')) df 以上数据拥有固定的频率。在实际降采样过程中,待聚合的数据可以具有不固定的频率,Pandas会依据降采样频率自动定义聚合面元的边界,返回的值如下: Num 2022-12-091 2022-12-112 2022-12-133 2022-12-154 2022-12-175 2022-12-196 重新采样的频率为W,类似于以W(周)为单位的group by对象,可对其聚合运算,代码如下: df.resample('w').sum() 20221211对应的周范围为2022125到20201211,20201218对应的周范围为20201212到20201218,20221225对应的周范围为20221219到20221225,返回的值如下: Num 2022-12-113 2022-12-1812 2022-12-256 Pandas中常见的聚合函数与方法有count()、sum()、mean()、max()/min()、cummax()/cummin()、idxmax()/argmax()等。当DataFrame或Series中存在时间索引列时,可运用resample()方法对其进行聚合运算,其原理类似于DataFrame或Series对象后接groupby()。当涉及多种聚合运算时,同样可运用agg()方法。应用举例,代码如下: df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['订单数','接单时间'], index_col='接单时间' ) df.resample('Q').agg(['count','sum','mean','max','min']) 更多有关agg()方法的应用见后续groupby()章节,返回的值如下: 订单数 countsummeanmaxmin 接单时间 2020-06-30362.0000002.02.0 2020-09-307284.0000009.01.0 2020-12-313279.00000011.07.0 2021-03-3100NaNNaNNaN 2021-06-30252.5000003.02.0 2021-09-30121179.75000017.02.0 2021-12-313227.3333339.04.0 当相关日期不是时间索引列时,可通过resample()中的on参数进行指定,代码如下: df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['订单数','接单时间'],) df.resample('4M',on='接单时间').agg(['count','sum','mean']) 返回的值如下: 订单数 countsummean 接单时间 2020-04-30122.000000 2020-08-315112.200000 2020-12-317486.857143 2021-04-3000NaN 2021-08-31131114.538462 2021-12-314334.250000 2. 升采样 升采样(asfreq)用于将低频率数据转换为高频率数据; 在升采样中,目标频率必须是源频率的超时期(superperiod)。 利用asfreq对period对象进行频率转换,代码如下: a = pd.Period(2021,freq='A-Feb') a.asfreq('M',how='start')#将转换频率为2019-03 运行代码,输出的结果如下: Period('2020-03', 'M') 对比how='start'与how='end'运行结果的差别,代码如下: a.asfreq('M',how='end')#将频率转换为2020-02 运行代码,输出的结果如下: Period('2021-02', 'M') 利用asfreq,对同一频率的不同切割点进行切换,代码如下: a = pd.Period(2021,freq='A-Feb') a.asfreq('A-JUN') 输出的结果如下: Period('2021', 'A-JUN') 继续举例,代码如下: #ch05d015.ipynb import pandas as pd df = pd.read_excel( r'D:\\数据源\\B文件\\订单表.xlsx', usecols=['接单时间','产品','订单数'], index_col='接单时间' ).head(10) df.resample('Q').last().resample('M').asfreq() 以上代码最后一行采用的是链式写法,其中的last()方法是时间索引数据中的最后时段,返回的值如下: 接单时间 2020-06-30苹果醋2.0 2020-07-31NaNNaN 2020-08-31NaNNaN 2020-09-30异形件9.0 对比原始数据后会发现: 2020630的订单数2所采用的是2020/6/30的数据; 2020930的订单数9所采用的是2020/9/27的数据。如果换成first(),则代表时间索引数据中的初始时段,代码如下: df.resample('Q').first().resample('M').asfreq() 返回的值如下: 产品订单数 接单时间 2020-06-30蛋糕纸2.0 2020-07-31NaNNaN 2020-08-31NaNNaN 2020-09-30钢化膜1.0 对比原始数据后会发现: 2020630的蛋糕纸产品的订单数2所采用的是2020/4/28的数据; 2020930的钢化膜产品的订单数1所采用的是2020/7/28的数据。