Python数据分析与应用----家用热水器用户分析与事件识别
目录:
一、预处理热水器用户用水数据
1.删除冗余特征
2.划分用水事件
3.确定单次用水事件时长阈值
二、构建用水行为特征并筛选用水事件
1.构建用水时长与频率特征
2.构建水量与波动特征
3.筛选候选洗浴事件
三、构建行为事件分析的BP神经网络模型
1.构建神经网络模型
2.评价神经网络模型
一、预处理热水器用户用水数据
1.删除冗余特征
import pandas as pd
import numpy as np
data = pd.read_excel('data/original_data.xls')
print('初始状态的数据形状为:',data.shape)
# 删除冗余数据
data.drop(labels=['热水器编号','有无水流','节能模式'],axis=1,inplace=True)
print('删除冗余数据后的数据形状为:',data.shape)
data.to_csv('tmp/water_heart.csv',index=False)
初始状态的数据形状为: (18840, 12)
删除冗余数据后的数据形状为: (18840, 9)
由于本案例主要针对目标为用户,分析器洗浴行为规律,所以可以对一些无关特征进行删减。‘热水器编号‘并不涉及用户信息,可以剔除;‘有无流水‘可以通过‘水流量’体现;‘节能模式’也与分析用户无关,因此将这几个特征删去。
2.划分用水事件
所谓用水事件,就是指用水所做的某一件事。比如:用水洗浴、用水洗手、用水刷牙、用水洗菜等等,这样独立的单一的与用水有关的事情称作用水事件。在本案例中,一件完整的用水事件可能包含有大量连续的数据记录。比如洗衣服来来回回洗了好几遍,自然就会出现多条用水记录,而这多条的用水记录组成的就是这一次的洗衣用水事件。这里面涉及到一个阈值的问题。
符号 | 解释 |
t1 | 所有水流量不为0的用水行为发生的时间 |
T | 时间间隔阈值 |
对于序列t1构建其向前时差列和向后时差列,并将每一组时差与阈值T比较。若向前超出T,则记为新用水事件开始编号;若向后超出T,则记为本次用水事件结束编号。
用水停顿时间间隔,就是同一用水事件中用水停顿的间隔时长,平时手洗衣物,不会只洗一遍,与此同时用水不会一次用完,只会洗一次用一次水,这样就产生了用水停顿间隔。当然,不同的用水事件的用水停顿间隔也会有所不同,比如洗一次衣服中间的间隔与刷一次牙中间的间隔,显然后者会更短。
根据原始数据给出的特征,用水量为0表示此时用户用热水发生停顿或结束,用水量不为0表示用户正在使用热水。
// 划分用水事件
threshold = pd.Timedelta('4 min') // 阈值为分钟
data['发生时间'] = pd.to_datetime(data['发生时间'],format='%Y%m%d%H%M%S') // 转换时间格式
data = data[data['水流量']>0] // 提取水流量大于0的数据
// 相邻时间向前差分,比较是否大于阈值
sjks = data['发生时间'].diff() > threshold
// 令第一个时间为第一个用水事件的开始
sjks.iloc[0] = True
// 向后差分的结果
sjjs = sjks.iloc[1:]
// 将最后一个时间作为最后一个用水事件的结束时间
sjjs = pd.concat([sjjs,pd.Series(True)])
// 创建数据框,并定义用水事件序列
sj = pd.DataFrame(np.arange(1,sum(sjks)+1),columns=['事件序号'])
sj['事件起始编号'] = data.index[sjks==1]+1 // 定义用水事件的起始编号
sj['事件终止编号'] = data.index[sjjs==1]+1 // 定义用水事件的终止编号
print('当阈值为4分钟时事件的数目为:',sj.shape[0])
sj.to_csv('tmp/sj.csv',index=False)
在定义实践编号时加1是因为:计算差值都是与相邻后一个连续时间数据比对的,在这里2与3的差值比对为True,说明2-3的间隔时段大于T,3应当为下一事件的起点。
diff函数是从数学上来说,是将数据与平移后的数据进行比较得出的差异数据。从操作的意义上来说,是两条临近记录的差值,也就是一阶差分。
3.确定单次用水事件时长阈值
// 确定单次用水时长阈值
n = 4
threshold = pd.Timedelta(minutes = 5) // 专家阈值
data['发生时间'] = pd.to_datetime(data['发生时间'],format='%Y%m%d%H%M%S') // 转换时间格式
// 自定义函数:输入划分时间的时间阈值,得到划分的事件数
def event_nums(ts):
d = data['发生时间'].diff()>ts // 相邻时间向前差分,比较是否大于阈值
return d.sum() #直接返回事件数
//转换数据框,定义阈值列
dt = [pd.Timedelta(minutes=i) for i in np.arange(1,9,0.25)]
h = pd.DataFrame(dt,columns=['阈值'])
h['事件数'] = h['阈值'].apply(event_nums) // 计算每个阈值对应的事件数,调用了上面的event_num函数
h['斜率'] = h['事件数'].diff()/0.25 // 计算每两个相邻点对应的斜率
// 往前取n个斜率绝对值平均作为斜率指标
h['斜率指标'] = h['斜率'].abs().rolling(4).mean()
ts = h['阈值'][h['斜率指标'].idxmin()-n] //#这里ts得到的是4min,利用修正过后的索引对h[u'阈值']取值,idxmin()返回数组中最小值的索引.
//注:用idxmin返回最小值的Index,由于rolling_mean()自动计算的是前n个斜率的绝对值平均(根据下方列表可以通透地理解这个意思),所以结果要进行平移(-n).
if ts>threshold: //这里的意思是,如果上面计算得到的ts小鱼上面设定的专家阈值,就以ts为准,否则就降低为4.
ts = pd.Timedelta(minutes=4)
print('计算出的单次用水时长的阈值为:',ts)
计算出的单次用水时长的阈值为: 0 days 00:04:00
dt = [pd.Timedelta(minutes=i) for i in np.arange(1,9,0.25)]
h = pd.DataFrame(dt,columns=['阈值'])
这里用到的语法是列表推导,也叫作列表解析。这里arange用于创建等差数组,这里的数据最终会变化成时间数据格式,也就是说,这里的0.25代表每分钟的四分之一,也就是15秒,按照15秒为步长,进行取数据。再由h将其转化为DataFrame类型数据。
返回顶部
二、构建用水行为特征并筛选用水事件
1.构建用水时长与频率特征
发送阈值是指热水器传输数据的频率大小。如下图,在20:00:10时,还未显示用水,而在20:00:12时,热水器记录到用水行为,所以用水时间应当在10min-12min之间,考虑到网络不稳定会导致网络数据传输延时数分钟或数小时因素,,求平均值会导致误差较大,综合分析,“用水开始时间”为起始数据的时间减去“发送阈值”的一半。
// 构建用水时长与频率特征
// 读取热水器使用数据记录
data = pd.read_csv('tmp/water_heart.csv')
// 读取用水事件记录
sj = pd.read_csv('tmp/sj.csv')
data['发生时间'] = pd.to_datetime(data['发生时间'],format='%Y%m%d%H%M%S') # 转换时间格式
print(data.head())
// 构造特征---总用水时长
timeDel = pd.Timedelta('1 sec')
sj['事件开始时间'] = data.iloc[sj['事件起始编号']-1,0].values-timeDel
sj['事件结束时间'] = data.iloc[sj['事件终止编号']-1,0].values+timeDel
tmp1 = sj["事件结束时间"] - sj["事件开始时间"]
// print(tmp1)
sj["总用水时长"] = np.int64(tmp1)/1000000000
// print(sj["总用水时长"])
// 构造用水停顿事件
// 构造特征:停顿开始时间、停顿结束时间
// 停顿开始时间是从有水到无水流的时间,停顿结束时间是从无水到有水流的时间
for i in range(len(data)-1):
if(data.loc[i,'水流量'] != 0)&(data.loc[i+1,'水流量']==0):
data.loc[i+1,'停顿开始时间'] = data.loc[i+1,'发生时间'] - timeDel
if(data.loc[i,'水流量'] == 0)&(data.loc[i+1,'水流量']!=0):
data.loc[i+1,'停顿结束时间'] = data.loc[i+1,'发生时间'] + timeDel
print(data.head())
// 提取停顿开始时间与结束时间所对应行号,放在数据框stop中
indStopStart = data.index[data['停顿开始时间'].notnull()]+1
indStopEnd = data.index[data['停顿结束时间'].notnull()]+1
Stop = pd.DataFrame(data={'停顿开始编号':indStopStart[:-1],'停顿结束编号':indStopEnd[1:]})
print(Stop)
// 计算停顿时长,并放在数据框stop中,停顿时长=停顿结束时间-停顿开始时间
tmp2 = data.loc[indStopEnd[1:]-1,"停顿结束时间"]
print(tmp2)
tmp3 = data.loc[indStopStart[:-1]-1,"停顿开始时间"]
print(tmp3)
tmp4 = tmp2.values-tmp3.values
Stop["停顿时长"] = np.int64(tmp4)/1000000000
// 将每次停顿与事件匹配,停顿的开始时间要大于事件的开始时间,且停顿的结束时间要小于事件的结束时间
for i in range(len(sj)):
Stop.loc[(Stop["停顿开始编号"] > sj.loc[i,"事件起始编号"]) &
(Stop["停顿结束编号"] < sj.loc[i,"事件终止编号"]),
"停顿归属事件"] = i+1
// 删除停顿次数为0的事件
Stop = Stop[Stop["停顿归属事件"].notnull()]
// 构造特征 用水事件停顿总时长、停顿次数、停顿平均时长、用水时长,用水/总时长
stopAgg = Stop.groupby("停顿归属事件").agg({"停顿时长":sum,"停顿开始编号":len})
sj.loc[stopAgg.index - 1,"总停顿时长"] = stopAgg.loc[:,"停顿时长"].values
sj.loc[stopAgg.index-1,"停顿次数"] = stopAgg.loc[:,"停顿开始编号"].values
sj.fillna(0,inplace=True) // 对缺失值用0插补
stopNo0 = sj["停顿次数"] != 0 // 判断用水事件是否存在停顿
sj.loc[stopNo0,"平均停顿时长"] = sj.loc[stopNo0,"总停顿时长"]/sj.loc[stopNo0,"停顿次数"]
sj.fillna(0,inplace=True) // 对缺失值用0插补
sj["用水时长"] = sj["总用水时长"] - sj["总停顿时长"] // 定义特征用水时长
// 定义特征 用水/总时长
sj["用水/总时长"] = sj["用水时长"] / sj["总用水时长"]
print('用水事件用水时长与频率特征构造完成后数据的特征为:\n',sj.columns)
print('用水事件用水时长与频率特征构造完成后数据的前5行5列特征为:\n',sj.iloc[:5,:5])
2.构建水量与波动特征
用水量也是识别改事件是否为洗浴事件的重要特征。例如,用水时间中的洗漱事件比洗浴事件有停顿次数多、用水总量少、平均用水少的特点。手洗大量衣物的事件比洗浴事件有停顿次数多、用水总量多、平均用水量多的特点。
同时,用水波动也是区分不同用水事件的关键。一般在洗漱事件中,刷牙和洗脸的用水量完全不同;在一次手洗衣物事件中,。每次用水的量和停顿时间相差却不大。
data["水流量"] = data["水流量"] / 60 // 原单位L/min,现转换为L/sec
sj["总用水量"] = 0 // 给总用水量赋一个初始值0
for i in range(len(sj)):
Start = sj.loc[i,"事件起始编号"]-1
End = sj.loc[i,"事件终止编号"]-1
if Start != End:
for j in range(Start,End):
if data.loc[j,"水流量"] != 0:
sj.loc[i,"总用水量"]=(data.loc[j + 1,"发生时间"]-data.loc[j,"发生时间"]).seconds*data.loc[j,"水流量"] + sj.loc[i,"总用水量"]
sj.loc[i,"总用水量"] = sj.loc[i,"总用水量"] + data.loc[End,"水流量"] * 2
else:
sj.loc[i,"总用水量"] = data.loc[Start,"水流量"] * 2
sj["平均水流量"] = sj["总用水量"] / sj["用水时长"] // 定义特征 平均水流量
// 构造特征:水流量波动
// 水流量波动=∑(((单次水流的值-平均水流量)^2)*持续时间)/用水时长
sj["水流量波动"] = 0 //给水流量波动赋一个初始值0
for i in range(len(sj)):
Start = sj.loc[i,"事件起始编号"] - 1
End = sj.loc[i,"事件终止编号"] - 1
for j in range(Start,End + 1):
if data.loc[j,"水流量"] != 0:
slbd = (data.loc[j,"水流量"] - sj.loc[i,"平均水流量"])**2
slsj = (data.loc[j + 1,"发生时间"] - data.loc[j,"发生时间"]).seconds
sj.loc[i,"水流量波动"] = slbd * slsj + sj.loc[i,"水流量波动"]
sj.loc[i,"水流量波动"] = sj.loc[i,"水流量波动"] / sj.loc[i,"用水时长"]
// 构造特征:停顿时长波动
// 停顿时长波动=∑(((单次停顿时长-平均停顿时长)^2)*持续时间)/总停顿时长
sj["停顿时长波动"] = 0 // 给停顿时长波动赋一个初始值0
for i in range(len(sj)):
// 当停顿次数为0或1时,停顿时长波动值为0,故排除
if sj.loc[i,"停顿次数"] > 1:
for j in Stop.loc[Stop["停顿归属事件"] == (i+1),"停顿时长"].values:
sj.loc[i,"停顿时长波动"] = ((j - sj.loc[i,"平均停顿时长"])**2) * j + sj.loc[i,"停顿时长波动"]
sj.loc[i,"停顿时长波动"] = sj.loc[i,"停顿时长波动"] / sj.loc[i,"总停顿时长"]
print('用水量和波动特征构造完成后数据的特征为:\n',sj.columns)
print('用水量和波动特征构造完成后数据的前5行5列特征为:\n',sj.iloc[:5,:5])
3.筛选候选洗浴事件
sj_bool = (sj['用水时长'] >100) & (sj['总用水时长'] > 120) & (sj['总用水量'] > 5)
sj_final = sj.loc[sj_bool,:]
sj_final.to_excel('tmp/sj_final.xlsx',index = False)
print('筛选出候选洗浴事件前的数据形状为:',sj.shape)
print('筛选出候选洗浴事件后的数据形状为:',sj_final.shape)
//筛选出候选洗浴事件前的数据形状为: (172, 15)
//筛选出候选洗浴事件后的数据形状为: (76, 15)
返回顶部
三、构建行为事件分析的BP神经网络模型
1.构建神经网络模型
BP神经网络算法:
基本语法及参数含义:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPClassifier
from sklearn.externals import joblib
// 读取数据
Xtrain = pd.read_excel('tmp/sj_final.xlsx')
ytrain = pd.read_excel('data/water_heater_log.xlsx')
test = pd.read_excel('data/test_data.xlsx')
// 训练集测试集区分。
x_train, x_test, y_train, y_test = Xtrain.iloc[:,5:],test.iloc[:,4:-1],ytrain.iloc[:,-1],test.iloc[:,-1]
// 标准化
stdScaler = StandardScaler().fit(x_train)
x_stdtrain = stdScaler.transform(x_train)
x_stdtest = stdScaler.transform(x_test)
// 建立模型
bpnn = MLPClassifier(hidden_layer_sizes = (17,10), max_iter = 200, solver = 'lbfgs',random_state=45)
bpnn.fit(x_stdtrain, y_train)
// 保存模型
joblib.dump(bpnn,'water_heater_nnet.m')
print('构建的模型为:\n',bpnn)
2.评价神经网络模型
from sklearn.metrics import classification_report
from sklearn.metrics import roc_curve
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
bpnn = joblib.load('water_heater_nnet.m') // 加载模型
y_pred = bpnn.predict(x_stdtest) // 返回预测结果
print('神经网络预测结果评价报告:\n',classification_report(y_test,y_pred))
//绘制roc曲线图
plt.rcParams['font.sans-serif'] = 'SimHei' //显示中文
plt.rcParams['axes.unicode_minus'] = False //显示负号
fpr, tpr, thresholds = roc_curve(y_pred,y_test) // 求出TPR和FPR
plt.figure(figsize=(6,4)) // 创建画布
plt.plot(fpr,tpr) // 绘制曲线
plt.title('用户用水事件识别ROC曲线')//标题
plt.xlabel('FPR') // x轴标签
plt.ylabel('TPR') // y轴标签
plt.savefig('用户用水事件识别ROC曲线.png') // 保存图片
plt.show()## 显示图形
返回顶部