数据分析与挖掘实战-家用电器用户行为分析与事件识别

家用电器用户行为分析与事件识别

  • 背景
    • 居民使用家电过程中,会因为地区气候、区域不同、年龄差异,形成不同的使用习惯,若能深入了解这些习惯,针对性地开发新功能,便能开拓市场。
    • 本案例以热水器为例,分析用户行为。在热水器用户行为分析过程中,用水事件识别最为关键。
  • 目标
    • 由于热水器可能用于各种事件而不仅仅是洗浴,要求根据收集到的数据,分析用户行为。
    • 基于热水器采集到的时间序列数据,将顺序排列的离散的用水时间节点依据水流量和停顿时间间隔划分为不同大小的时间区间,每个区间是一个可理解的一次完整用水事件,并且以这一次完整的用水事件作为一个基本事件,将时间序列数据划分为独立的用水事件并且识别出洗浴事件。
    • 基于此,厂商对智能操作和节能运行进行优化。
  • 分析
    • 用水事件划分与识别
      • 对用户的历史用水数据进行选择性抽取,构建专家样本。
      • 对构建的样本数据集进行数据探索和数据预处理,包括探索用水事件时间间隔的分布、规约冗余属性、识别用水数据的缺失值,并对缺失值进行处理,根据建模的需要进行属性构造等。
      • 根据上述处理,对用水样本数据建立用水事件时间间隔识别模型和划分一次完整的用水事件模型,再在一次完整用水事件划分结果的基础上,剔除短暂用水事件,缩小识别范围。
      • 根据上一步得到的建模样本数据,建立洗浴事件识别模型,并进行模型评价分析。
  • 处理过程

    • 数据获取
      • 由机器自动记录,数据量大,本案例数据为无放回随机抽取200家用户从2014年1月1日到2014年12月31日的用水记录作为建模数据。
    • 数据探索
      • 用水停顿时间间隔为两条水流量不为0的流水记录之间的时间间隔,为了探究用户真实的用水停顿时间间隔分布情况,统计用水停顿的时间间隔并做频率分布直方图。
      • 可以知道,正常的两次用水间隔在3~7分钟。
    • 数据预处理
      • 数据特点
        • 数据涉及上万用户且每个用户的每天数据多达数万条,存在缺失值、与分析无关的属性或许未直接反映用水事件的属性。
      • 数据规约
        • 属性规约
          • 对用户的洗浴行为的一般性分析,所以“热水器编号”属性多余,可以去除;而且,“有无水流”属性可以通过“水流量”反映,可以去除,减少特征复杂;“节能模式”都为“关”,没有意义。
        • 数值规约
          • 当“开关机状态”为“关”且“水流量”为0,说明热水器不在工作,记录可以规约掉。
      • 数据变换
        • 由于目标是对洗浴事件进行识别,这就需要识别出哪些状态是完整的用水事件,继而识别出其中的洗浴事件。由于一次完整的用水事件是根据水流量和停顿时间间隔的阈值去划分的,所以本案例还建立了阈值寻优模型。同时,为了提高在大量用水事件中寻找洗浴事件的效率,本案例建立了筛选规则剔除明显不是洗浴事件的记录,得到建模数据样本。
        • 一次完整用水事件的划分模型
          • 哪些连续的数据是一次完整的用水事件
            • 水流量不为0表示正在使用热水,水流量为0则表示用热水停顿或者结束。如果水流量为0的状态记录之间的时间间隔超过一个阈值T(也就是前后是两个不同事件),那么从该段水流量为0的记录向前找到最后一条水流量不为0的记录作为上一次用水事件的结束,向后找到水流量不为0的记录作为下一个用水事件的开始。
          • 操作流程
            • Step1:读取数据记录,识别到第一条水流量不为0的数据记录,记为R1按照顺序,下一条水流量不为0的记录记为R2。
            • Step2:计算Ri与Ri+1之间的差值记为gapi,若gapi大于阈值T则Ri和Ri+1之间的数据记录不能认为是同一个用水事件。同时将Ri+1记录作为新的读取数据记录的开始,返回Step1;若gapi小于阈值T,则将Ri+1与Ri之间的数据记录划分到一个用水事件,并记录下一个水流量不为0的数据记录为Ri+2。
            • Step3:循环Step2,直到数据记录读取完毕,结束事件划分。
          • 代码实现
            • 数据预处理.py
            • # -*- coding:UTF-8 -*
              import pandas as pd
              
              
              def attrStatute():
                  '''
                  属性规约
                  :return:
                  '''
                  rawData = pd.read_excel('data/original_data.xls').drop(columns=["热水器编号","有无水流", "节能模式"])
                  return rawData
              
              
              def valueStatute():
                  '''
                  数值规约
                  :return:
                  '''
                  data = pd.read_excel('data/water_heater.xls')
                  newData = data[data['开关机状态'].isin(['关']) & data['水流量'].isin([0])]
                  return newData
              
              
              def divideEvent():
                  '''
                  事件划分
                  :return:
                  '''
                  # 阈值设置为4分钟
                  threshold = pd.Timedelta('4 min')
                  inputFile = 'data/water_heater.xls'
                  outputFile = 'data/dividesequence.xls'
                  data = pd.read_excel(inputFile)
                  data['发生时间'] = pd.to_datetime(data['发生时间'], format='%Y%m%d%H%M%S')
                  # 只保留水流量大于0的记录
                  data = data[data['水流量'] > 0]
                  # 将原数据的发生时间做一阶差分,得到一个时间差值的dataframe
                  d = data['发生时间'].diff() > threshold
                  # 累计求和编号数据
                  data['事件编号'] = d.cumsum() + 1
                  data.to_excel(outputFile)
              
              
              if __name__ == '__main__':
                  # attrStatute().to_excel("data/water_heater.xls")
                  # valueStatute().to_excel("data/water_heater2.xls")z
                  divideEvent()
        • 用水事件阈值寻优模型
          • 原因
            • 不同地区,不同季节,使用热水器停顿时长是不同的,固定一个阈值做上述处理是不合适的,所以考虑到在不同的时间段内要更新阈值,故建立阈值寻优模型来寻找最优阈值。
          • 实现方法
            • 指定连续的阈值尝试划分,得到的事件个数必定不太相同,绘制折线图不难发现曲线平稳处符合大多数人的要求,利用斜率来刻画寻找这个最优点。
          • 代码
            • 阈值寻优模型.py
            • # -*- coding: utf-8 -*-
              """
              在1-9分钟进行阈值寻优
              """
              import numpy as np
              import pandas as pd
              
              
              def event_num(ts):
                  '''
                  得到事件数目
                  :param ts:
                  :return:
                  '''
                  d = data[u'发生时间'].diff() > ts
                  return d.sum() + 1
              
              
              if __name__ == '__main__':
                  inputfile = 'data/water_heater.xls'
                  # 使用以后四个点的平均斜率
                  n = 4
                  threshold = pd.Timedelta(minutes=5)
                  data = pd.read_excel(inputfile)
                  data[u'发生时间'] = pd.to_datetime(data[u'发生时间'], format='%Y%m%d%H%M%S')
                  data = data[data[u'水流量'] > 0]
                  dt = [pd.Timedelta(minutes=i) for i in np.arange(1, 9, 0.25)]
                  # 定义阈值列
                  h = pd.DataFrame(dt, columns=[u'阈值'])
                  # 计算每个阈值对应的事件数
                  h[u'事件数'] = h[u'阈值'].apply(event_num)
                  # 计算每两个相邻点对应的斜率
                  h[u'斜率'] = h[u'事件数'].diff()/0.25
                  # 采用后n个的斜率绝对值平均作为斜率指标
                  h[u'斜率指标'] = pd.DataFrame(h[u'斜率'].abs()[len(h)-n:]).rolling(2).mean()
                  ts = h[u'阈值'][h[u'斜率指标'].idxmin() - n]
                  if ts > threshold:
                      ts = pd.Timedelta(minutes=4)
                  print(ts)
              
            • 结果为4分钟
        • 属性构造
          • 本案例研究的是用水行为,可以构造四类指标:时长指标、频率指标、用水的量化指标以及用水的波动指标。都是为了模型服务建立特征项。
        • 筛选“洗浴事件”
          • 剔除短暂用水事件,剩余的为候选洗浴事件。
      • 数据清洗
        • 对异常值和缺失值处理,比较简单不再叙述。
    • 数据挖掘建模
      • 经过数据预处理这一核心步骤,得到的数据已经是满足建模要求的数据了。
      • 由于洗浴事件和普通用水事件在特征上不同,根据用水日志,将洗浴事件作为训练样本训练神经网络,然后根据训练好的网络检验新的数据。
      • 选取11个特征作为神经网络的输入:洗浴时间点、总用水时长、总停顿时长、平均停顿时长、停顿次数、用水时长、用水时长/总用水时长、总用水量、平均用水量水流量波动和停顿时长波动。训练BP神经网络给定的输出为0或1,其中1表示为洗浴事件,0表示不是洗浴事件。
      • 使用keras库训练神经网络。
      • 代码
        • # -*- coding: utf-8 -*-
          """
          利用神经网络挖掘建模
          """
          import pandas as pd
          from keras.models import Sequential
          from keras.layers.core import Dense, Dropout, Activation
          
          inputFile1 = 'data/train_neural_network_data.xls'
          inputFile2 = 'data/test_neural_network_data.xls'
          testoutputfile = 'data/test_output_data.xls'
          data_train = pd.read_excel(inputFile1)
          data_test = pd.read_excel(inputFile2)
          y_train = data_train.iloc[:, 4].as_matrix()
          x_train = data_train.iloc[:, 5:17].as_matrix()
          y_test = data_test.iloc[:, 4].as_matrix()
          x_test = data_test.iloc[:, 5:17].as_matrix()
          
          # 建模
          model = Sequential()
          # 添加输入层、隐藏层的连接
          model.add(Dense(input_dim=11, units=17))
          # 以Relu函数为激活函数
          model.add(Activation('relu'))
          # 添加隐藏层、隐藏层的连接
          model.add(Dense(input_dim=17, units=10))
          # 以Relu函数为激活函数
          model.add(Activation('relu'))
          # 添加隐藏层、输出层的连接
          model.add(Dense(input_dim=10, units=1))
          # 以sigmoid函数为激活函数
          model.add(Activation('sigmoid'))
          # 编译模型,损失函数为binary_crossentropy,用adam法求解
          model.compile(loss='binary_crossentropy', optimizer='adam')
          
          model.fit(x_train, y_train, epochs=100, batch_size=1)
          model.save_weights('data/net.model')
          
          r = pd.DataFrame(model.predict_classes(x_test), columns=['预测结果'])
          pd.concat([data_test.iloc[:, :5], r], axis=1).to_excel(testoutputfile)
          rst = model.predict(x_test)
          print(rst)
  • 补充说明

    • 案例参考书《Python数据分析与挖掘实战》
    • 具体数据集代码见我的GitHub
    • 与原书有借鉴,但是较大改动代码
    • 修复了原书一些旧版本代码错误
    • 常见错误(均是因为keras版本改动)
      • 1
        • TypeError: Dense can accept only 1 positional arguments ('units',), but you passed the following positional arguments: [23, 34]
        • 解决方法
          • 在Dense中写好参数名称改为Dense(input_dim=23,units=34)
      • 2
        • ValueError: ('Some keys in session_kwargs are not supported at this time: %s', dict_keys(['class_mode']))
        • 解决方法
          • 模型编译代码中去掉class_mode这一属性
      • 3
        • UserWarning: The nb_epoch argument in fit has been renamed epochs
        • 解决方法
          • 修改代码中的“nb_epoch”为“epochs”即可

猜你喜欢

转载自blog.csdn.net/zhouchen1998/article/details/86572596
今日推荐