数据分析学习笔记--家用热水器用户行为分析与实践识别(代码)

数据分析学习笔记--家用热水器用户行为分析与实践识别(代码),第1张

案例基于热水器采集的时间序列数据,将顺序排列的离散的用水时间节点根据水流量和停顿时间间隔划分不同大小的时间区间,每个时间区间可以理解成一次完整用水事件。

定义挖掘目标如下:
1.根据热水器采集到的数据,划分一次完整的用水事件
2.在划分好的一次完整用水事件中,识别出洗浴事件

数据分析步骤:
1.预处理热水器用户用水数据,划分一次完整的用水事件
a.删除冗余属性[‘热水器编号’,‘有无水流’,‘节能模式’,‘当前设置温度’]
b.通过阈值寻优的方式找出最优阈值
c.在大量的状态记录中划分出哪些连续的数据是一次完整的用水事件。
读取数据记录,识别所有水流量不为0的状态记录,将它们的发生时间记为序列t1
对序列t1构建其向前时差列和向后时差列,并分别与阈值进行比较。向前时差超过阈值T,则将它记为新的用水事件的开始编号;如果向后时差超过阈值T,则将其记为用水事件的结束编号。

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)

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)

n = 4 #使用以后四个点的平均斜率
threshold = pd.Timedelta(minutes = 5) #专家阈值
data['发生时间'] = pd.to_datetime(data['发生时间'], 
    format = '%Y%m%d%H%M%S')
data = data[data['水流量'] > 0] #只要流量大于0的记录
# 自定义函数:输入划分时间的时间阈值,得到划分的事件数
def event_num(ts):
    d = data['发生时间'].diff() > ts #相邻时间作差分,比较是否大于阈值
    return d.sum() + 1 #这样直接返回事件数
dt = [pd.Timedelta(minutes = i) for i in np.arange(1, 9, 0.25)]
h = pd.DataFrame(dt, columns = ['阈值']) #转换数据框,定义阈值列
h['事件数'] = h['阈值'].apply(event_num) #计算每个阈值对应的事件数
h['斜率'] = h['事件数'].diff()/0.25 #计算每两个相邻点对应的斜率
#往前取n个斜率绝对值平均作为斜率指标
h['斜率指标']= h['斜率'].abs().rolling(4).mean()
ts = h['阈值'][h['斜率指标'].idxmin() - n]
#用idxmin返回最小值的Index,由于rolling_mean()计算的是前n个斜率的绝对值平均
#所以结果要进行平移(-n)
if ts > threshold:
    ts = pd.Timedelta(minutes = 4)
print('计算出的单次用水时长的阈值为:',ts)

2.构建用水行为特征并筛选用水事件
用水时长:事件开始时间、事件结束时间、洗浴时间点、用水时长、总用水时长和用水时长/总用水时长
用水频率:停顿时长、总停顿时长、平均停顿时长、停顿次数

import pandas as pd 
import numpy as np
# 读取热水器使用数据记录
data = pd.read_excel('../data/water_hearter.xlsx') 
# 读取用水事件记录
sj = pd.read_csv('../tmp/sj.csv')
data["发生时间"] = pd.to_datetime(data["发生时间"],
    format = "%Y%m%d%H%M%S") # 转换时间格式

# 构造特征:总用水时长
timeDel = pd.Timedelta("1 sec")
sj["事件开始时间"] = data.iloc[sj["事件起始编号"]-1,0].values- timeDel
sj["事件结束时间"] = \
data.iloc[sj["事件终止编号"]-1,0].values + timeDel
sj['洗浴时间点'] = [i.hour for i in sj["事件开始时间"]]
tmp1 = sj["事件结束时间"] - sj["事件开始时间"]
sj["总用水时长"] = np.int64(tmp1)/1000000000 

# 构造用水停顿事件
# 构造特征“停顿开始时间”、“停顿结束时间”
# 停顿开始时间指从有水流到无水流,停顿结束时间指从无水流到有水流
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,"停顿结束时间"] = \
        data.loc[i , "发生时间"] + timeDel 
# 提取停顿开始时间与结束时间所对应行号,放在数据框Stop中
indStopStart = data.index[data["停顿开始时间"].notnull()]+1
indStopEnd = data.index[data["停顿结束时间"].notnull()]+1
Stop = pd.DataFrame(data = {"停顿开始编号":indStopStart[:-1],
                            "停顿结束编号":indStopEnd[1:]}) 
# 计算停顿时长,并放在数据框stop中,停顿时长=停顿结束时间-停顿结束时间
tmp2 = data.loc[indStopEnd[1:]-1,"停顿结束时间"]
tmp3 = data.loc[indStopStart[:-1]-1,"停顿开始时间"]
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])

用水量:总用水量、平均水流量
用水波动:水流量波动和停顿时长波动

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个比较宽松的条件筛选掉那些非常短暂的用水事件,确定不可能为洗浴事件的数据就删除,剩余的事件称为“候选洗浴事件”。
一次用水事件中总用水量小于5升;用水时长小于100秒;总用水时长小于120秒。

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)


3.构建行为事件分析的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
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)



# 代码 9-8
# 模型预测
# 模型预测
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()## 显示图形


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/733108.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存