数据分析-家用热水器用户行为分析与事件识别

发布时间 2023-03-27 20:48:04作者: Binnie
  1. 显示数据
    import pandas as pd
    import numpy as np
    data = pd.read_excel('D:/人工智能&软件工程/数据挖掘与分析/data/original_data.xls')
    data[u'发生时间'] = pd.to_datetime(data[u'发生时间'], format='%Y%m%d%H%M%S')  # 将该特征转成日期时间格式(***)
    data = data[data[u'水流量'] > 0]  # 只要流量大于0的记录
    # print len(data) #7679
    
    data[u'用水停顿时间间隔'] = data[u'发生时间'].diff() / np.timedelta64(1, 'm')  # 将datetime64[ns]转成 以分钟为单位(*****)
    data = data.fillna(0)  # 替换掉data[u'用水停顿时间间隔']的第一个空值
    print(data.head())
    data_explore = data.describe().T
    data_explore['null'] = len(data)-data_explore['count']
    explore = data_explore[['min','max','null']]
    explore.columns = [u'最小值',u'最大值',u'空值数']
    print(explore)
          热水器编号                发生时间 开关机状态 加热中 保温中 有无水流  实际温度 热水量  水流量 节能模式  \
    2    R_00001 2014-10-19 07:01:56     关   关   关    无  30°C  0%    8    关   
    56   R_00001 2014-10-19 07:38:16     关   关   关    无  30°C  0%    8    关   
    381  R_00001 2014-10-19 09:46:38     关   关   关    无  29°C  0%   16    关   
    382  R_00001 2014-10-19 09:46:40     关   关   关    无  29°C  0%   13    关   
    384  R_00001 2014-10-19 09:47:15     关   关   关    有  29°C  0%   20    关   
    
        加热剩余时间 当前设置温度    用水停顿时间间隔  
    2      0分钟   50°C    0.000000  
    56     0分钟   50°C   36.333333  
    381    0分钟   50°C  128.366667  
    382    0分钟   50°C    0.033333  
    384    0分钟   50°C    0.583333  
              最小值          最大值  空值数
    水流量       8.0    77.000000  0.0
    用水停顿时间间隔  0.0  2093.366667  0.0
  2. 探索分析热水器的水流量状况
    import pandas as pd
    import matplotlib.pyplot as plt
    inputfile = 'D:/人工智能&软件工程/数据挖掘与分析/data/original_data.xls'  # 输入的数据文件
    data = pd.read_excel(inputfile)  # 读取数据
    
    # 查看有无水流的分布
    # 数据提取
    lv_non = pd.value_counts(data['有无水流'])['']
    lv_move = pd.value_counts(data['有无水流'])['']
    # 绘制条形图
    fig = plt.figure(figsize=(6 ,5))  # 设置画布大小
    plt.rcParams['font.sans-serif'] = 'SimHei'  # 设置中文显示
    plt.rcParams['axes.unicode_minus'] = False
    plt.bar(x=['',''], height=[lv_non,lv_move], width=0.4, alpha=0.8,
          color='skyblue')
    plt.xticks([index for index in range(2)], ['',''])
    plt.xlabel('水流状态')
    plt.ylabel('记录数')
    plt.title('不同水流状态记录数学号:3041')
    plt.show()
    plt.close()
    # 查看水流量分布
    water = data['水流量']
    # 绘制水流量分布箱型图
    fig = plt.figure(figsize=(5 ,8))
    plt.boxplot(water,
                patch_artist=True,
                labels = ['水流量'],  # 设置x轴标题
                boxprops = {'facecolor':'lightblue'})  # 设置填充颜色
    plt.title('水流量分布箱线图学号:3041')
    # 显示y坐标轴的底线
    plt.grid(axis='y')
    plt.show()

     

  3. 删除冗余属性
    import pandas as pd
    import numpy as np
    data = pd.read_excel('D:/人工智能&软件工程/数据挖掘与分析/data/original_data.xls')
    print('初始状态的数据形状为:', data.shape)
    # 删除热水器编号、有无水流、节能模式属性
    data.drop(labels=["热水器编号","有无水流","节能模式"],axis=1,inplace=True) 
    print('删除冗余属性后的数据形状为:', data.shape)
    data.to_csv('D:/人工智能&软件工程/数据挖掘与分析/tmp/water_heart学号:3041.csv',index=False)
    初始状态的数据形状为: (18840, 12)
    删除冗余属性后的数据形状为: (18840, 9)
  4. 划分用水事件
    import pandas as pd
    import numpy as np
    # 读取数据
    data = pd.read_csv('D:/人工智能&软件工程/数据挖掘与分析/tmp/water_heart学号:3041.csv')
    # 划分用水事件
    threshold = pd.Timedelta('4 min')  # 阈值为4分钟
    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('D:/人工智能&软件工程/数据挖掘与分析/tmp/sj学号:3041.csv',index = False)
    当阈值为4分钟的时候事件数目为: 172
  5. 确定单次用水事件时长阈值
    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  # 计算每两个相邻点对应的斜率
    h['斜率指标']= h['斜率'].abs().rolling(4).mean()  # 往前取n个斜率绝对值平均作为斜率指标
    ts = h['阈值'][h['斜率指标'].idxmin() - n]
    # 用idxmin返回最小值的Index,由于rolling_mean()计算的是前n个斜率的绝对值平均
    # 所以结果要进行平移(-n)
    if ts > threshold:
        ts = pd.Timedelta(minutes=4)
    print('计算出的单次用水时长的阈值为:',ts)
    计算出的单次用水时长的阈值为: 0 days 00:04:00
  6. 构建用水时长与用水频率属性
    import pandas as pd
    import numpy as np
    data = pd.read_csv ('D:/人工智能&软件工程/数据挖掘与分析/tmp/water_heart学号:3041.csv')  # 读取热水器使用数据记录
    sj = pd.read_csv('D:/人工智能&软件工程/数据挖掘与分析/tmp/sj学号:3041.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["事件开始时间"]]
    sj["总用水时长"] = np.int64(sj["事件结束时间"] - sj["事件开始时间"])/1000000000 + 1
    
    
    # 构造用水停顿事件
    # 构造属性“停顿开始时间”、“停顿结束时间”
    # 停顿开始时间指从有水流到无水流,停顿结束时间指从无水流到有水流
    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中,停顿时长=停顿结束时间-停顿结束时间
    Stop["停顿时长"] = np.int64(data.loc[indStopEnd[1:]-1,"停顿结束时间"].values-
                         data.loc[indStopStart[:-1]-1,"停顿开始时间"].values)/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])
    用水事件用水时长与频率属性构造完成后数据的属性为:
     Index(['事件序号', '事件起始编号', '事件终止编号', '事件开始时间', '事件结束时间', '洗浴时间点', '总用水时长',
           '总停顿时长', '停顿次数', '平均停顿时长', '用水时长', '用水/总时长'],
          dtype='object')
    用水事件用水时长与频率属性构造完成后数据的前5行5列属性为:
        事件序号  事件起始编号  事件终止编号              事件开始时间              事件结束时间
    0     1       3       3 2014-10-19 07:01:55 2014-10-19 07:01:57
    1     2      57      57 2014-10-19 07:38:15 2014-10-19 07:38:17
    2     3     382     385 2014-10-19 09:46:37 2014-10-19 09:47:16
    3     4     405     405 2014-10-19 11:50:16 2014-10-19 11:50:18
    4     5     408     408 2014-10-19 13:56:20 2014-10-19 13:56:22
  7. 构建用水量和用水波动属性
    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)):
        if sj.loc[i,"停顿次数"] > 1: # 当停顿次数为0或1时,停顿时长波动值为0,故排除
            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])
    用水量和波动属性构造完成后数据的属性为:
     Index(['事件序号', '事件起始编号', '事件终止编号', '事件开始时间', '事件结束时间', '洗浴时间点', '总用水时长',
           '总停顿时长', '停顿次数', '平均停顿时长', '用水时长', '用水/总时长', '总用水量', '平均水流量', '水流量波动',
           '停顿时长波动'],
          dtype='object')
    用水量和波动属性构造完成后数据的前5行5列属性为:
        事件序号  事件起始编号  事件终止编号              事件开始时间              事件结束时间
    0     1       3       3 2014-10-19 07:01:55 2014-10-19 07:01:57
    1     2      57      57 2014-10-19 07:38:15 2014-10-19 07:38:17
    2     3     382     385 2014-10-19 09:46:37 2014-10-19 09:47:16
    3     4     405     405 2014-10-19 11:50:16 2014-10-19 11:50:18
    4     5     408     408 2014-10-19 13:56:20 2014-10-19 13:56:22
  8. 筛选候选洗浴事件
    sj_bool = (sj['用水时长'] >100) & (sj['总用水时长'] > 120) & (sj['总用水量'] > 5)
    sj_final = sj.loc[sj_bool,:]
    sj_final.to_excel('D:/人工智能&软件工程/数据挖掘与分析/tmp/sj_final学号:3041.xlsx',index=False)
    print('筛选出候选洗浴事件前的数据形状为:',sj.shape)
    print('筛选出候选洗浴事件后的数据形状为:',sj_final.shape)
    筛选出候选洗浴事件前的数据形状为: (172, 16)
    筛选出候选洗浴事件后的数据形状为: (75, 16)
  9. 构建神经网络模型
    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('D:/人工智能&软件工程/数据挖掘与分析/tmp/sj_final.xlsx')
    ytrain = pd.read_excel('D:/人工智能&软件工程/数据挖掘与分析/data/water_heater_log.xlsx')
    test = pd.read_excel('D:/人工智能&软件工程/数据挖掘与分析/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)
    构建的模型为:
     MLPClassifier(hidden_layer_sizes=(17, 10), random_state=45, solver='lbfgs')
    神经网络预测结果评价报告:
                   precision    recall  f1-score   support
    
               0       0.39      0.92      0.55        12
               1       0.95      0.54      0.69        37
    
        accuracy                           0.63        49
       macro avg       0.67      0.73      0.62        49
    weighted avg       0.82      0.63      0.66        49
  10. 神经网络模型评价
    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曲线学号:3041')##标题
    plt.xlabel('FPR')## x轴标签
    plt.ylabel('TPR')## y轴标签
    plt.savefig('用户用水事件识别ROC曲线学号:3041.png')## 保存图片
    plt.show()## 显示图形