事故时间特征序列分析汇总
- 引言
- 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
输出结果为:
最后就是绘图了
至此全部就梳理完毕了,撒花~