数据分析之python进阶1.0(时间序列数据处理)

131 阅读12分钟

python还可以这样用

合适的方法可以简化代码提高效率

image.png

列表推导

例:创建了一个空列表,然后通过for循环来给列表添加新元素,并且需要通过if语句保证x在一定的数值范围

传统写法

data = []
for x in range(-5, 5):
    if x >= -2:
        data.append(x**2)
print(data)

列表推导

data = [x**2 for x in range(-5, 5) if x >= -2]
print(data)

优势:可以看出,相比于传统的代码编写,列表推导可以使代码更简洁

函数使用

例:求数组a,b的欧几里得相似度

import numpy as np
def eculidDisSim(x, y):
    return np.sqrt(sum(pow(a-b, 2) for a, b in zip(x, y)))
a = np.array([1, 2, 3])
b = np.array([6, 5, 4])
print(eculidDisSim(a, b))

优势:函数能提高应用的模块性,和代码的重复利用率。比如封装一个文本分词函数、相似性计算函数等

异常处理

每当在运行时检测到程序错误时,python就会引发异常。可以使用try...except... 进行异常处理。

try:
    # 执行try代码
except:
    # 执行应对异常发生时的代码

join和split

join用来连接字符串,split恰好相反, 拆分字符串的。常用于字符串数据处理

strs = ' '.join(['my', 'name', 'is', 'peiqi'])
print(strs)

li = 'my name is qiaozhi'.split(' ')
print(li)

lambda函数

lambda函数是一种比较小的匿名函数,其功能是执行某种简单的表达式或运算,而无需完全定义函数。

func = lambda x, y : x * y
print(func(2, 3))

func = lambda x : x ** 2 + 3
print(func(2))

map函数

map()是一种内置的Python函数,方法会将一个函数映射到序列的每一个元素上,生成新序列,包含所有函数返回值。

image.png

def func(x):
    return  x ** 2
squ = list(map(func, [1, 2, 3, 4, 5]))
print(squ)

filter函数

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。

def func(x):
    return  x % 2 == 1
li = filter(func, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(list(li))

itertools模块

itertools模块中 的函数大多是返回各种迭代器对象,其中很多函数的作用需要我们平时要写很多代码才能达到,然而在运行效率上反而更低。

import itertools
x = itertools.accumulate(range(10))  # 累加
print(list(x))
x = itertools.chain(range(3), range(4), [8, 9, 10])  # 合并列表
print(list(x))
x = itertools.product('AB', range(2))  # 笛卡尔积
print(list(x))

除了以上几个基础方法外,数据分析常用的3个库:numpy、pandas和matplotlib详解可以参考下面这篇博客。

juejin.cn/post/718556…

高效处理带有时间序列数据

什么是时间序列?

时间序列( Time Series)是-种重要的结构化数据形式,在多个时间点观察或测量到的任何事物都可以形成一段时间序列,分为:

  • 定期的时间序列:数据点是根据某种规律定期出现的(每10秒、每15分钟)

假设我们定期每秒获取一次数据,那一分钟内我们将获取到6条带时间序列的数据

  • 不定期的时间序列:没有固定的时间单位或单位之间的偏移量

数据的获取可能是一分钟一条或者是20秒一条

日期和时间数据

  • Python标准库包含用于日期(date) 和时间( time)数据的数据类型,主要用到的模块有datetimetime
  • Pandas提供了一组标准的时间序列处理工具和数据算法,如进行切片/切块、聚合、对定期/不定期的时间序列进行重采样等。

datetime模块中的数据类型

类型说明
date以公历形式存储日历日期(年、月、日)
time将时间存储为时、分、秒、毫秒
datetime存储日期和时间
timedelta表示两个datetime值之间的差(日、秒、毫秒)

datetime.datetime或简称为datetime,是用的最多的数据类型

datetime类型

  • 查看现在的日期和时间(记录到毫秒),为datetime类型
from datetime import datetime
now = datetime.now()
print(now)
print(type(now))

  • 查看年月日
from datetime import datetime
now = datetime.now()
print(now.year, now.month, now.day)

timedelta类型

  • 两个datetime对象之间的时间差(日、秒、毫秒)为timedelta类型
from datetime import timedelta, datetime
now = datetime.now()
delta = now - datetime(2022, 12, 31)
print(delta)
print(type(delta))

  • 查看相差的日、秒、毫秒
from datetime import timedelta, datetime
now = datetime.now()
delta = now - datetime(2022, 12, 31)
print(delta.days, delta.seconds, delta.microseconds)

常用于特征构建

  • 给datetime对象加上或减去一个或多个timedelta,会产生一个新的对象。
from datetime import timedelta, datetime
now = datetime.now()
new = now + timedelta(10)
print(new)
print(type(new))
new = now - 2 * timedelta(10)
print(new)

字符串和datetime的相互转化

在日常数据处理中,经常会出现把日期储存成字符串或把字符串储存成日期的情况

字符串与datatime之间通常需要进行转换,有以下三种转换方式:

  • Python标准库函数:

    • 日期转换成字符串:利用str或strftime
    • 字符串转换成日期: datetime.strptime
  • 第三方库函数:

    • dateutil.parser时间解析函数

日期转换成字符串

  • 利用str将日期转换成字符串
from datetime import datetime
stamp = datetime.now()
print(str(stamp))

  • 更改datetime格式
符号描述范围
%y两位数的年份表示0~99
%Y四位数的年份表示0000~9999
%m月份01~12
%d月份内的一天0~31
%H24小时制小时0~23
%l12小时制小时01~12
%M分钟数00~59
%S秒数
%a本地简化星期名称
%A本地完整星期名称
%b本地简化月份名称
%B本地完整月份名称
%c本地相应的日期表示和时间表示
%j年内的一天001~366
%p本地A.M.或者P.M.的等价符
%U一年中的星期数,星期天为一周的开始0~53
%w星期,星期天为一周的开始0~6
%W一年中的星期数,星期一为一周的开始0~53
%x本地对应的日期表示
%X本地相应的时间表示
%Z当前时区的名称
%%%号本身
  • 利用strftime将日期转换为字符串,并设置格式
from datetime import datetime
stamp = datetime.now()
print(stamp.strftime('%Y-%m-%d'))

字符串转换成日期

  • 利用strptime将字符串转换成日期,需传入格式
from datetime import datetime
value = '2023-01-08'
print(datetime.strptime(value, '%Y-%m-%d'))

pandas时间序列基础

  • Pandas最基本的时间序列类型就是以时间戳(通常以Python字符串或datetime对象表示)为索引的Series
import pandas as pd
import numpy as np
dates = ['2017-06-20', '2017-06-21', '2017-06-22', '2017-06-23', '2017-06-24', '2017-06-25', '2017-06-26', '2017-06-27']
ts= pd.Series(np.random.randn(8), index=pd.to_datetime(dates))
print(ts)

通过字符串选取子集

注意: 通过字符串选取子集时,传入的字符串需要能被解析成日期

import pandas as pd
import numpy as np
dates = ['2017-06-20', '2017-06-21', '2017-06-22', '2017-06-23', '2017-06-24', '2017-06-25', '2017-06-26', '2017-06-27']
ts= pd.Series(np.random.randn(8), index=pd.to_datetime(dates))
print(ts)
print(ts['2017-06-22'])
print(ts['22/06/2017'])

第二种格式可以查询出结果,但是会发出警告

  • 可传入只包含年或年月的字符串,选取该范围内的子集
import pandas as pd
import numpy as np
dates = ['2017-06-20', '2017-06-21', '2017-06-22', '2017-06-23', '2017-06-24', '2017-06-25', '2017-06-26', '2017-06-27']
ts= pd.Series(np.random.randn(8), index=pd.to_datetime(dates))
print(ts)
print(ts['2017-06'])

  • 选取时间范围内的子集
import pandas as pd
import numpy as np
dates = ['2017-06-20', '2017-06-21', '2017-06-22', '2017-06-23', '2017-06-24', '2017-06-25', '2017-06-26', '2017-06-27']
ts= pd.Series(np.random.randn(8), index=pd.to_datetime(dates))
print(ts)
print(ts['2017-06-20': '2017-06-24'])

带有重复索引的时间序列

在某些应用场景中,通常存在一一个时间点上有多个观测数据的情况,例如

import pandas as pd
import numpy as np
dates = pd.DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03', '2023-01-03'])
dup_ts = pd.Series(np.arange(5), index=dates)
print(dup_ts)

当索引唯一

import pandas as pd
import numpy as np
dates = pd.DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03', '2023-01-03'])
dup_ts = pd.Series(np.arange(5), index=dates)
print(dup_ts['2023-01-01'])

当索引不唯一

import pandas as pd
import numpy as np
dates = pd.DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03', '2023-01-03'])
dup_ts = pd.Series(np.arange(5), index=dates)
print(dup_ts['2023-01-02'])

针对上述情况,通常使用如下两种方式进行操作:

  • index.is_unique检查 索引日期是否唯一
import pandas as pd
import numpy as np
dates = pd.DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03', '2023-01-03'])
dup_ts = pd.Series(np.arange(5), index=dates)
print(dup_ts.index.is_unique)

  • 使用groupby() 对数据进行分组聚合

    • 使用groupby()对数据进行分组,并传入level= 0(索引的唯一一层)
    • import pandas as pd
      import numpy as np
      dates = pd.DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03', '2023-01-03'])
      dup_ts = pd.Series(np.arange(5), index=dates)
      grouped = dup_ts.groupby(level=0)
      print(pd.Series(grouped))
      
    • 之后可使用mean()和count()等函数对分组后的数据按时间戳进行聚合
    • import pandas as pd
      import numpy as np
      dates = pd.DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-02', '2023-01-03', '2023-01-03'])
      dup_ts = pd.Series(np.arange(5), index=dates)
      grouped = dup_ts.groupby(level=0)
      print(grouped.mean())
      print(grouped.count())
      

日期的范围、频率及移动

pd.date_range() 可用 于生成指定长度的日期索引,默认产生按天计算的时间点,即日期范围。参数可以是:

  • 起始结束日期
  • 或者只有一个起始或结束日期,加一个时间段参数

起始结束日期

import pandas as pd
print(pd.date_range('20230101', '20230110'))
print(pd.date_range(start='20230101', periods=10))
print(pd.date_range(end='20230110', periods=10))

三者输出的结果是一样的

生成日期范围

如果想要生成一一个特殊频率的日期索引,如一个由每月最后一个 工作日组成的日期索引,直接加上参数freq='BM' ( BM表示business end of month)

import pandas as pd
print(pd.date_range('20230101', '20231201', freq='BM'))

时间频率

Pandas中的频率是由一个基础频率和一个乘数组成的。基础频率通常以一个字符串别名表示,如'M'表示每月, ‘H’表示每小时。

import pandas as pd
print(pd.date_range('20230101', '20231201', freq='3M'))

常于分析周期性数据时使用

基础频率表

别名偏移量类型说明
DDay每日历日
BBusinessDay每工作日
HHour每小时
T或minMinute每分钟
MMonthEnd每月最后一个日历日
BMBusinessMonthEnd每月最后一个工作日
WOM-1MONWeekOfMonth产生每月第一、第二、第三或第四周的星期几,如WOM-3FRI表示每月第3个星期五
Q-JANQuarterEnd对于指定月份( JAN、FEB、MAR.....DEC)结束的年度,每季度最后一个月的最后一个日历日
A-JANYearEnd对于指定月份的最后一个日历日

日期偏移量

对于每个基础频率,都有一个被称为日期偏移量( date offset) 的对象与之对应。可以通过实例化日期偏移量来创建某种频率:

from  pandas.tseries.offsets import Hour,Minute
hour = Hour()
print(hour)
four_hours = Hour(4)
print(four_hours)

大部分偏移量对象可以通过加法进行连接:

from  pandas.tseries.offsets import Hour,Minute
print(Hour(2) + Minute(30))

有些频率所描述的时间点并不是均匀分隔的。例如‘M’和‘BM'就取决于每月的天数,对于后者,还要考虑月末是不是周末,将这些称为锚点偏移量( anchored offset )

对于这种情况,我们需要移动(超前或者滞后)数据

移动(超前或者滞后)数据

移动( shifting) 指的是沿着时间轴将数据前移或后移,保持索引不变。

import pandas as pd
import numpy as np
ts = pd.Series(np.random.randn(4), index=pd.date_range('1/1/2018', periods=4, freq='M'))
print(ts)

shift(): 保持索引不变数据前移/后移

import pandas as pd
import numpy as np
ts = pd.Series(np.random.randn(4), index=pd.date_range('1/1/2018', periods=4, freq='M'))
print(ts)
print(ts.shift(2))
print(ts.shift(-2))

  • 说明:
  •   ts.shift(2)把ts的结果往后移两位,即2018-01-31的数据移动至2018-03-31,2018-02-28的数据移动至2018-04-30,由于2018-01-31和2018-02-28不存在前两位的数据,因此移动后两者数据为NaN。同理,ts.shift(-2)把ts的结果往前移两位,因此2018-03-31和2018-04-30两者数据为NaN

通过偏移量对日期进行位移

将当前时间now直接加.上一个实例化的日期偏移量,即可得到相应偏移后的时间戳

from pandas.tseries.offsets import Day, MonthEnd
from datetime import datetime
now = datetime.now().date()
print(now)
print(now + 3 * Day())