时间序列特征分析汇总(以2012-2019年槽罐车事故数据为例)

   日期:2020-11-11     浏览:128    评论:0    
核心提示:事故时间特征序列分析汇总引言1 数据读取和时间特征转化1.1 先将时间的字段组合成为统一的形式1.2 再将标准形式的日期字段转化为datetime1.3 提取年、月、日字段信息1.4 查看日期是在第几周1.5 查看日期是在周几2 特定字段的数据提取2.1 字符串extract方法使用2.2 apply/map结合正则表达使用3 单字段多特征进行计数统计3.1 将所有的特征都添加到列表中,转化为Series数据进行计数3.2 使用字典计数的方式进行统计3.3 使用pd.explode()方法提取多特征转化为

事故时间特征序列分析汇总

  • 引言
  • 1 数据读取和时间特征转化
    • 1.1 先将时间的字段组合成为统一的形式
    • 1.2 再将标准形式的日期字段转化为datetime
    • 1.3 提取年、月、日字段信息
    • 1.4 查看日期是在第几周
    • 1.5 查看日期是在周几
  • 2 特定字段的数据提取
    • 2.1 字符串extract方法使用
    • 2.2 apply/map结合正则表达使用
  • 3 单字段多特征进行计数统计
    • 3.1 将所有的特征都添加到列表中,转化为Series数据进行计数
    • 3.2 使用字典计数的方式进行统计
    • 3.3 使用pd.explode()方法提取多特征转化为Series进行计数
  • 4 绘制时间序列事故图
    • 4.1 按照年份进行绘制
    • 4.2 按照季度进行绘制
    • 4.3 按照月份进行绘制
    • 4.4 按照小时进行绘制

手动反爬虫: 原博地址

 知识梳理不易,请尊重劳动成果,文章仅发布在CSDN网站上,在其他网站看到该博文均属于未经作者授权的恶意爬取信息

如若转载,请标明出处,谢谢

引言

在进行实际的的业务处理过程中,常常会和时间特征打交道,这里就进行真实数据的的时间序列处理,并将整个业务的基本流程梳理一下,数据为2012-2019 年的槽罐车事故的统计数据,已上传至资源(包含全部运行之后的ipynb文件),内容样式如下:

1 数据读取和时间特征转化

在进行数据导入之前先加载常用的模块,和设置绘制图形的字体,然后再导入要操作的数据,代码如下

import warnings
warnings.filterwarnings('ignore')
import re
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

plt.rcParams['font.family'] = ['sans-serif']
plt.rcParams['font.sans-serif'] = ['SimHei']

data_load = pd.read_excel('2012-2019tank_data.xlsx')
data_load.head()

输出结果为:

观察前三列可以发现关于时间特征有年份、日期和时间,那么习惯的操作就是将默认读取的“时间”数据转化为datetime数据类型,方便提取具体的年、月、日或者进一步的提取事故时间所在一周第几天,方便统计周末的事故情况

处理方式:
统一的将时间的字段转化为‘xx-xx-xx’形式(‘年-月-日’),然后使用pd.to_datetime()方法转化为datetime数据类型。比如这里的年份和日期进行组合(发生时间已经是具体的时间类型了,不需要转化了)

1.1 先将时间的字段组合成为统一的形式

data_load['date'] = data_load['年份'].map(str) + '-' + data_load['日期'].map(lambda x: str(x).replace('.','-'))
data_load['date']

输出结果为:(这里转化为统一格式后,数据的类型是object)

1.2 再将标准形式的日期字段转化为datetime

data_load['date'] = pd.to_datetime(data_load['date'])
data_load['date']

输出结果为:(这里转化为统一格式后,数据的类型就是目标类型datetime64)

1.3 提取年、月、日字段信息

由于经常和时间特征打交道,这里直接就封装一个函数,只要把刚刚处理完毕的日期字段传递到函数中,就可以获得对应的年、月、日字段信息了,代码如下

def get_year_month_day(df,time_col):
    
    '''Extract the year, month, and day of the time field data'''
    
    df[time_col]  = pd.to_datetime(df[time_col])
    df['year'] = df[time_col].dt.year
    df['month'] = df[time_col].dt.month
    df['day'] = df[time_col].dt.day
    return df

其中第一个参数导入的数据对应的DataFrame数据,第二个参数就是DataFrame数据里面的时间字段,使用方式如下

df = get_year_month_day(data_load,'date')
df.head()

输出结果为:(至此就完成了时间特征的提取)

提取时间信息的函数还可以自己根据需求进行添加,比如你要获取所在日期所在的第几周,或者是一周的第几天等,如下

1.4 查看日期是在第几周

1.5 查看日期是在周几

这里需要注意一下,返回的结果是0-6之间的数字,为了和大家普遍的理解认知相符,建议对结果+1,这样1就代表周一,7就代表周日了,方便理解。

2 特定字段的数据提取

提取需要的目标字段进行分析,其余的数据就暂时不进行处理,习惯上会将所有的字段重新使用英文来命令,方便之后的操作(不用再切换输入法了)

df['time'] = df['发生时间']
df['type'] = df['事故类型']
df['result'] = df['事故影响(人员)']
data = df[['date','year','month','day','time','type','result']]
data.head()

输出结果为:

2.1 字符串extract方法使用

比如对于事故造成的影响,需要提取里面的死亡人数,重伤人数和轻伤人数,如果单纯的使用excel表格处理,也是可以做的,但是工作量还是有点大,采用python操作就很简单三行代码搞定了,这一部分使用了正则表达式提取数据的方式,可以参考一下:正则表达式的分组及在pandas中的实用操作

data['death_num'] = data['result'].str.extract('(?P<death_num>\d+)人死亡')
data['injury_num'] =  data['result'].str.extract('(?P<injury_num>\d+)人重伤')
data['wound_num'] =  data['result'].str.extract('(?P<wound_num>\d+)人轻伤')
data

输出结果为:(当然也可以自定一个函数,然后使用apply的方式进行数据提取)

2.2 apply/map结合正则表达使用

如果使用apply或者map的方式,代码如下

data['death'] = data['result'].apply(lambda x: re.search(r'(\d+)人死亡',x).group(1) if re.search(r'(\d+)人死亡',x) else 0)
data['injury'] = data['result'].apply(lambda x: re.search(r'(\d+)人重伤',x).group(1) if re.search(r'(\d+)人重伤',x) else 0)
data['wound'] = data['result'].apply(lambda x: re.search(r'(\d+)人轻伤',x).group(1) if re.search(r'(\d+)人轻伤',x) else 0)

data

输出结果如下:(使用apply)

输出结果如下:(使用map方法,个人感觉这两种方法对我来说使用上没有差别,选择一种就可以了)

3 单字段多特征进行计数统计

比如这里的type事故类型字段,pandas里面有个value_counts方法计数,这个功能是很好用的,但是当单字段中出现了多个特征的时候,这个方法不会默认给我们进行多特征的切分统计,示例如下

data.type.value_counts()

输出结果为:(这里就会把单元格中的数据作为一次的统计标准,只要是出现全部一样,就统计一次计数)

这种情况下直接进行value_counts()就没有办法满足要求了, 因此需要对这个字段的数据进行处理,处理的方式,有三种,但是根本的方式都是转化为列表,然后再进行统计计数

3.1 将所有的特征都添加到列表中,转化为Series数据进行计数

基本步骤是首先创建一个空列表保存数据,接着就是遍历字段中所有的内容,按照特定的字符进行split切分,将切分的结果再extend到空列表中储存,最后把这个列表转化为Series数据进行计数。

ls = []
for item in data.type:
    ls.extend(item.split(','))

pd.Series(ls).value_counts()

输出结果为:(可以发现基本上满足要求,但是有一些噪音数据需要进一步处理,这种情况是没有办法避免的,因为数据都是人为上报的,多多少少会存在着手动录入的错误,这里的泄露也是人为录入的错误)

处理里面的噪音数据后再进行统计计数,代码如下

ls = []
for item in data.type.str.replace('。',',').str.replace('露','漏'):
    ls.extend(item.split(','))

pd.Series(ls).value_counts()

输出结果为:(结果实现了单字段多特征的统计计数,接下来直接就可以进行plot绘图)

比如简单的进行柱状图绘制,选取前15条数据

3.2 使用字典计数的方式进行统计

使用字典中的get方式就可以实现对列表中的数据进行计数,代码如下

d = { }
for item in data.type.str.replace('。',',').str.replace('露','漏'):
    txt_list = item.split(",")
    for w in txt_list:
        d[w] = d.get(w,0) +1
d

输出结果为:(这样直接就形成了一个字典对应的数据集)

这里的d变量也可以直接转化为Series数据,代码如下

acc_type = pd.Series(list(d.values()),index = d.keys())
acc_type

输出结果为:(通过字典来构造Series数据)

3.3 使用pd.explode()方法提取多特征转化为Series进行计数

这种方法要求pandas的版本在0.25.0以上,可以参考前面的博客关于explode方法的使用详解,这里就直接进行操作,代码如下。一些常用的处理数据的过程,习惯直接封装为函数,下次再使用的时候调用传入相应的参数就可以了,这里封装的接口就是直接传入要统计的字段名称即可

def explode_acc(df_col):
    df = df_col.value_counts().to_frame().reset_index()
    df['index'] = df['index'].apply(lambda x: x.split(',') if type(x) == str else str(x))
    df = df.explode('index').groupby('index').sum()
    return df
    
explode_acc(data.type.str.replace('。',',').str.replace('露','漏'))

输出结果为:(至此使用三种方式进行单字段多特征的统计计数就完成了)

4 绘制时间序列事故图

比如这里选择呈现的是不同时间的事故起数和死亡人数,直接将过程的处理封装为函数,然后进行相应参数的传递即可,但是注意一下字段的数据类型,都应该是数值类型

def accident_count(df,column_1,column_2):
    df_ = df.groupby(column_1).agg({ column_1:'count',column_2:'sum'}) 
    df_['account'] = (df_[column_1] / df_[column_1].sum()).map(lambda x:f'{round(x*100,2)}%')
    return df_

4.1 按照年份进行绘制

直接就是传入数据字段和要统计的死亡人数字段,代码如下,后两个参数就对应字段的名称,但是对应字段的数字类型应该是为数值型

data.death_num = data.death_num.astype(int)
data_year = accident_count(data,'year','death_num')
data_year

输出结果为:(这样输出结果的第一列就是事故起数,第二列就为死亡人数,最后一列为事故占比)

既然数据已经统计出来了,接着就是进行数据的绘图操作,代码如下

fig,ax = plt.subplots(figsize = (12,8))

ax.set_ylim(0,80)

ax.bar(data_year.index,data_year.year,width=0.6,edgecolor='k',color='gray',alpha = 0.8,label = '每年事故总量/起')
ax.legend(loc=(0.754,0.93))
ax.set_xlabel('事故发生年份')
ax.set_ylabel('事故数量/起')
ax.spines['top'].set_visible(False)

for i,j,k in zip(data_year.index,data_year.year.values,data_year.account.values):
    ax.text(i-0.13,j/2-0.1,k,fontsize = 10)
    ax.text(i-0.05,j+3,"%.0f" %j,fontsize = 10)
    
    
ax1 = ax.twinx()
ax1.spines['top'].set_visible(False)
ax1.set_ylim(0,80)
ax1.plot(data_year.index,data_year.death_num.values,label = '每年事故伤亡总人数/人',color = 'k',marker ='o')
ax1.legend(loc=(0.754,0.86))
ax1.set_ylabel('事故死亡总人数/人')
plt.savefig(r'1.png',dpi =200)

输出结果为:(绘图基本上就是这个过程,也是可以直接封装函数的,方便后续的调用)

但是有一点就是在封装的过程中遇到一个问题就是,y轴坐标显示的问题,为了解决这个问题,先设计一个简单的算法进行y轴最大值的计算,这样保证绘制出来的图形比较合理,算法函数如下

#为了方便自主出图,设计个算法进行自动去纵轴坐标值
def ceil_up(num):
    ''' 个位数的值小于5的直接取5,大于5的向上取10 十位数的值个位小于5的直接取5,个位大于5的,十位进1 百位数的值十位小于5的直接取5,十位大于5的,百位进1 千位数的值百位小于5的直接取5,百位大于5的,千位进1 万位数的值千位小于5的直接取5,千位大于5的,万位进1 '''
    if num < 10:
        if num <= 5:
            num = 5
        else:
            num =10
    elif num<100:
        if int(str(num)[-1]) < 5:
            num = int(str(num)[0])*10 + 10
        else:
            num = (int(str(num)[0]) + 1)*10
    elif num<1000:
        if int(str(num)[-2]) < 5:
            num = int(str(num)[0])*100 + 50
        else:
            num = (int(str(num)[0])+1)*100
    elif num <10000:
        if int(str(num)[-3]) < 5:
            num = int(str(num)[0])*1000 + 500
        else:
            num = (int(str(num)[0])+1)*1000 
    elif num <100000:
        if int(str(num)[-4]) < 5:
            num = int(str(num)[0])*10000 + 5000
        else:
            num = (int(str(num)[0])+1)*10000
    return num

最后就是直接把出图的过程封装为函数,方便调用,同时保留几个参数传入的接口。这里使用的是表示时间的字段和要保存图片的路径,方便直接一步到位,代码如下

def plot_accident_figure(df_time,period,dir_path):
    import os
    plt.rcParams['font.family'] = ['sans-serif']
    plt.rcParams['font.sans-serif'] = ['SimHei']

    fig,ax = plt.subplots(figsize = (12,8))
    y1_max_data = df_time.iloc[:,0].max()
    ax.set_ylim(0,ceil_up(y1_max_data))   #根据直接的要求调整y轴的显示
# ax.set_yticks(range(0,ceil_up(y1_max_data)+1,50))

    ax.bar(df_time.index,df_time.iloc[:,0],width=0.6,edgecolor='k',color='gray',alpha = 0.8,label = f'每{period}事故总量/起')
# ax.legend(loc=(0.754,0.93))
    ax.set_xlabel(f'事故发生{period}')
    ax.set_ylabel('事故数量/起')
    ax.spines['top'].set_visible(False)
    
    if all(type(x)==int for x in df_time.index.tolist()):
        for i,j,k in zip(df_time.index,df_time.iloc[:,0].values,df_time.account.values):
            ax.text(i-0.13,j/2-0.1,k,fontsize = 10)
            ax.text(i-0.05,j+1,"%.0f" %j,fontsize = 10)
    else:
        for i,j,k in zip(range(0,len(df_time.index)),df_time.iloc[:,0].values,df_time.account.values):
            ax.text(i-0.13,j/2-0.1,k,fontsize = 10)
            ax.text(i-0.05,j+1,"%.0f" %j,fontsize = 10)

    
    ax1 = ax.twinx()
    ax1.spines['top'].set_visible(False)
    y2_max_data = df_time.iloc[:,1].max()
    ax1.set_ylim(0,ceil_up(y2_max_data))  #根据直接的要求调整y轴的显示
# ax1.set_yticks(range(0,ceil_up(y2_max_data)+10,50))
    ax1.plot(df_time.index,df_time.death_num.values,label = f'每{period}事故伤亡总人数/人',color = 'r',marker ='o')
# ax1.legend(loc=(0.754,0.86))
    ax1.set_ylabel('事故伤亡总人数/人')
    plt.savefig(os.path.join(dir_path,f'每{period}事故统计图.png'),dpi =200)

关于这个函数中,除了刚刚讲到的y轴最大值的处理外,还有一个问题就是文本标记的设定,其中如果标签值是数字时候,那么做文本标记的时候就相对于简单,但是如果是文本数据时候,就出现问题了,所以这里需要进行两种判断,最后按照标签的数据类型进行文本标记

使用这个封装的函数试一下绘图如何,代码如下

path = r'C:\Users\86177\Desktop'
plot_accident_figure(data_year,'年份',path)

输出结果为:(可以发现两侧的y轴的最大值会跟着数据的最大值变化,而不需要人为指定)

4.2 按照季度进行绘制

前面已经封装好函数了,这里只需要进行季度数据的获取即可,代码如下

data['season'] = pd.cut(data['month'],[0,3,6,9,12],labels=['第一季度','第二季度','第三季度','第四季度'])
data.tail()

输出结果为:(对月份字段进行cut切割就可以得到季度的数据)

然后将季度的字段传到封装好的函数中去,代码如下

data_season = accident_count(data,'season','death_num')
data_season

输出结果为:

plot_accident_figure(data,'季度',path)

输出结果为:

4.3 按照月份进行绘制

月份数据之前已经提取完毕了,这里就方便多了,直接就可以拿过来用,调用两个函数就完成数据结果和绘图输出

data_month = accident_count(data,'month','death_num')
data_month

输出结果为:

调用绘图函数直接绘制图像

plot_accident_figure(data_month,'月份',path)

输出结果为:

4.4 按照小时进行绘制

这一部分就是对小时的字段进行处理了,难度还是有一点,代码如下

data['one_hour'] = pd.cut(data['time'].map(str).str[:2].astype(int),list(range(-1,24)),labels=[f'{i}:00-{i+1}:00' for i in range(24)])
data

输出结果为:(就是提取前两个字符然后转化为数字,按照数字的取值进行cut切分,最后就可以设置对应的labels)


那么两小时的字段处理也就类似了

data['two_hour'] = pd.cut(data['time'].map(str).str[:2].astype(int),list(range(-1,25,2)),labels=[f'{i}:00-{i+2}:00' for i in range(0,24,2)])
data[['time','one_hour','two_hour']]

输出结果为:

采用两小时的字段数据进行统计和绘图,操作如下

data_two_hour = accident_count(data,'two_hour','death_num')
data_two_hour

输出结果为:

最后就是绘图了

至此全部就梳理完毕了,撒花~

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服