天天看點

Python資料分析與應用----家用熱水器使用者分析與事件識别

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)      
Python資料分析與應用----家用熱水器使用者分析與事件識别

在定義實踐編号時加1是因為:計算內插補點都是與相鄰後一個連續時間資料比對的,在這裡2與3的內插補點比對為True,說明2-3的間隔時段大于T,3應當為下一事件的起點。

Python資料分析與應用----家用熱水器使用者分析與事件識别

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=['門檻值'])      
Python資料分析與應用----家用熱水器使用者分析與事件識别

這裡用到的文法是清單推導,也叫作清單解析。這裡arange用于建立等差數組,這裡的資料最終會變化成時間資料格式,也就是說,這裡的0.25代表每分鐘的四分之一,也就是15秒,按照15秒為步長,進行取資料。再由h将其轉化為DataFrame類型資料。

​​傳回頂部​​

二、建構用水行為特征并篩選用水事件

1.建構用水時長與頻率特征

Python資料分析與應用----家用熱水器使用者分析與事件識别

發送門檻值是指熱水器傳輸資料的頻率大小。如下圖,在20:00:10時,還未顯示用水,而在20:00:12時,熱水器記錄到用水行為,是以用水時間應當在10min-12min之間,考慮到網絡不穩定會導緻網絡資料傳輸延時數分鐘或數小時因素,,求平均值會導緻誤差較大,綜合分析,“用水開始時間”為起始資料的時間減去“發送門檻值”的一半。

Python資料分析與應用----家用熱水器使用者分析與事件識别
Python資料分析與應用----家用熱水器使用者分析與事件識别
Python資料分析與應用----家用熱水器使用者分析與事件識别
Python資料分析與應用----家用熱水器使用者分析與事件識别
Python資料分析與應用----家用熱水器使用者分析與事件識别
Python資料分析與應用----家用熱水器使用者分析與事件識别
// 建構用水時長與頻率特征

// 讀取熱水器使用資料記錄
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])      
Python資料分析與應用----家用熱水器使用者分析與事件識别

2.建構水量與波動特征

用水量也是識别改事件是否為洗浴事件的重要特征。例如,用水時間中的洗漱事件比洗浴事件有停頓次數多、用水總量少、平均用水少的特點。手洗大量衣物的事件比洗浴事件有停頓次數多、用水總量多、平均用水量多的特點。

Python資料分析與應用----家用熱水器使用者分析與事件識别

同時,用水波動也是區分不同用水事件的關鍵。一般在洗漱事件中,刷牙和洗臉的用水量完全不同;在一次手洗衣物事件中,。每次用水的量和停頓時間相差卻不大。

Python資料分析與應用----家用熱水器使用者分析與事件識别
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])      
Python資料分析與應用----家用熱水器使用者分析與事件識别

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神經網絡算法:

基本文法及參數含義:

Python資料分析與應用----家用熱水器使用者分析與事件識别
Python資料分析與應用----家用熱水器使用者分析與事件識别
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()## 顯示圖形