In [1]:
import numpy as np
import pandas as pd
from os.path import join

from pylab import rcParams
import matplotlib.pyplot as plt
%matplotlib inline
rcParams['figure.figsize'] = (14, 6)

plt.style.use('ggplot')
import nilmtk
from nilmtk import DataSet, TimeFrame, MeterGroup, HDFDataStore
from nilmtk.disaggregate import CombinatorialOptimisation
from nilmtk.utils import print_dict
from nilmtk.metrics import f1_score

import warnings
warnings.filterwarnings("ignore")

Demo of NILMTK v0.2 for BuildSys 2014

Downloading data

The full data set can be downloaded from the remote WikiEnergy database. The credentials are omitted here for security reasons.

In [2]:
# download_wikienergy(database_username, database_password, hdf_filename)

Loading data

In [3]:
data_dir = '/Users/nipunbatra/Downloads/'
we = DataSet(join(data_dir, 'wikienergy.h5'))
print('loaded ' + str(len(we.buildings)) + ' buildings')
loaded 239 buildings

Examine dataset metadata

In [4]:
print_dict(we.metadata)
  • meter_devices:
    • eGauge:
      • max_sample_period: 120
      • description: Measures circuit-level power demand. Comes with 12 CTs.
      • manufacturer_url: https://www.egauge.net
      • measurements:
        • {'type': 'active', 'upper_limit': 5000, 'lower_limit': 0, 'physical_quantity': 'power'}
      • sample_period: 60
      • wireless: False
      • model: eGauge
      • manufacturer: eGauge Systems LLC
  • creators:
    • Jim Shead
  • name: WikiEnergy
  • description: Several months of power data for more than 239 different homes.
  • number_of_buildings: 239
  • related_documents:
  • long_name: WikiEnergy database
  • contact: [email protected]
  • geo_location:
    • latitude: 30.292308
    • country: US
    • longitude: -97.699671
    • locality: Austin
  • publication_date: 2014
  • timezone: US/Central
  • schema: https://github.com/nilmtk/nilm_metadata/tree/v0.2
  • institution: Pecan Street Inc
  • subject: Disaggregated power demand from domestic buildings.

Examine metadata for a single house

In [5]:
building_number = 11
print_dict(we.buildings[building_number].metadata)
  • instance: 11
  • dataset: WikiEnergy
  • original_name: 434

Examine sub-metered appliances

In [6]:
elec = we.buildings[building_number].elec
elec.appliances
Out[6]:
[Appliance(type='fridge', instance=1),
 Appliance(type='dish washer', instance=1),
 Appliance(type='electric water heating appliance', instance=1),
 Appliance(type='spin dryer', instance=1),
 Appliance(type='electric furnace', instance=1),
 Appliance(type='sockets', instance=1),
 Appliance(type='sockets', instance=2),
 Appliance(type='air conditioner', instance=1),
 Appliance(type='sockets', instance=3),
 Appliance(type='sockets', instance=4)]

Wiring hierarchy of meters

In [7]:
elec.draw_wiring_graph();

Select all fridges

In [8]:
fridges = nilmtk.global_meter_group.select_using_appliances(type='fridge')

Proportion of energy per fridge

The energy consumed by each appliance can be expressed as a proportion of the household's total energy. Here we find the range of proportions for each fridge.

In [11]:
# Select a subset of fridges, otherwise the computation takes a long time
fridges_restricted = MeterGroup(fridges.meters[6:16])

proportion_per_fridge = fridges_restricted.proportion_of_upstream_total_per_meter()
10/10 ElecMeter(instance=15, building=116, dataset='WikiEnergy', appliances=[Appliance(type='fridge', instance=1)]) = 0.100
In [13]:
proportion_per_fridge.plot(kind='barh');
plt.title('Fridge energy as proportion of total building energy');
plt.xlabel('Proportion');
plt.ylabel('Fridge (<appliance instance>, <building instance>, <dataset name>)');
In [14]:
# How much energy does the largest-consuming fridge consume in kWh?
fridges.select(building=61).total_energy()
Calculating total_energy for ElecMeterID(instance=14, building=61, dataset='WikiEnergy') ...   
Out[14]:
active    79.120667
dtype: float64
In [15]:
fridges.select(building=61).plot();
plt.xlabel("Time");

Daily energy consumption across fridges in WikiEnergy data set

In [16]:
fridges_restricted = MeterGroup(fridges.meters[:20])
daily_energy = pd.DataFrame([meter.average_energy_per_period(offset_alias='D') 
                             for meter in fridges_restricted.meters])

daily_energy.plot(kind='hist');
plt.title('Histogram of daily fridge energy');
plt.xlabel('energy (kWh)');
plt.ylabel('occurences');
plt.legend().set_visible(False)

Plot sub-metered data for a single day

In [19]:
we.set_window(start='2014-04-01 00:00:00', end='2014-04-02 00:00:00')
elec.plot();
plt.xlabel("Time");

Plot fraction of energy consumption of each appliance

In [20]:
fraction = elec.submeters().fraction_per_meter().dropna()
10/10 ElecMeter(instance=11, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric water heating appliance', instance=1)])
In [21]:
# Create convenient labels
labels = elec.get_labels(fraction.index)
plt.figure(figsize=(6,6))
fraction.plot(kind='pie', labels=labels);

Select meters on the basic of appliance category

In [22]:
elec.select_using_appliances(category='heating')
Out[22]:
MeterGroup(meters=
  ElecMeter(instance=7, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric furnace', instance=1)])
  ElecMeter(instance=11, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric water heating appliance', instance=1)])
)
In [23]:
# Find all appliances with a particular type of motor
elec.select_using_appliances(category='single-phase induction motor')
Out[23]:
MeterGroup(meters=
  ElecMeter(instance=2, building=11, dataset='WikiEnergy', appliances=[Appliance(type='air conditioner', instance=1)])
  ElecMeter(instance=5, building=11, dataset='WikiEnergy', appliances=[Appliance(type='dish washer', instance=1)])
  ElecMeter(instance=6, building=11, dataset='WikiEnergy', appliances=[Appliance(type='spin dryer', instance=1)])
  ElecMeter(instance=7, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric furnace', instance=1)])
  ElecMeter(instance=10, building=11, dataset='WikiEnergy', appliances=[Appliance(type='fridge', instance=1)])
)

Training and disaggregation

In [24]:
# Train
co = CombinatorialOptimisation()
co.train(elec)
Training model for submeter 'ElecMeter(instance=2, building=11, dataset='WikiEnergy', appliances=[Appliance(type='air conditioner', instance=1)])'
Training model for submeter 'ElecMeter(instance=3, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=1)])'
Training model for submeter 'ElecMeter(instance=4, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=2)])'
Training model for submeter 'ElecMeter(instance=5, building=11, dataset='WikiEnergy', appliances=[Appliance(type='dish washer', instance=1)])'
Training model for submeter 'ElecMeter(instance=6, building=11, dataset='WikiEnergy', appliances=[Appliance(type='spin dryer', instance=1)])'
Training model for submeter 'ElecMeter(instance=7, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric furnace', instance=1)])'
Training model for submeter 'ElecMeter(instance=8, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=3)])'
Training model for submeter 'ElecMeter(instance=9, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=4)])'
Training model for submeter 'ElecMeter(instance=10, building=11, dataset='WikiEnergy', appliances=[Appliance(type='fridge', instance=1)])'
Training model for submeter 'ElecMeter(instance=11, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric water heating appliance', instance=1)])'
Done training!
In [25]:
for model in co.model:
    print_dict(model)
  • states: [ 0 887 2970]
  • training_metadata: ElecMeter(instance=2, building=11, dataset='WikiEnergy', appliances=[Appliance(type='air conditioner', instance=1)])
  • states: [ 0 13 77]
  • training_metadata: ElecMeter(instance=3, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=1)])
  • states: [ 0 11 12]
  • training_metadata: ElecMeter(instance=4, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=2)])
  • states: [ 0 197 1030]
  • training_metadata: ElecMeter(instance=5, building=11, dataset='WikiEnergy', appliances=[Appliance(type='dish washer', instance=1)])
  • states: [0]
  • training_metadata: ElecMeter(instance=6, building=11, dataset='WikiEnergy', appliances=[Appliance(type='spin dryer', instance=1)])
  • states: [ 0 15 520]
  • training_metadata: ElecMeter(instance=7, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric furnace', instance=1)])
  • states: [ 0 34 789]
  • training_metadata: ElecMeter(instance=8, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=3)])
  • states: [ 0 49 52]
  • training_metadata: ElecMeter(instance=9, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=4)])
  • states: [ 0 138 463]
  • training_metadata: ElecMeter(instance=10, building=11, dataset='WikiEnergy', appliances=[Appliance(type='fridge', instance=1)])
  • states: [ 0 17 34]
  • training_metadata: ElecMeter(instance=11, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric water heating appliance', instance=1)])
In [26]:
# Disaggregate
disag_filename = join(data_dir, 'wikienergy-disag.h5')
output = HDFDataStore(disag_filename, 'w')
co.disaggregate(elec.mains(), output)
output.close()
vampire_power = 321.0 watts
Estimating power demand for 'ElecMeter(instance=2, building=11, dataset='WikiEnergy', appliances=[Appliance(type='air conditioner', instance=1)])'
Estimating power demand for 'ElecMeter(instance=3, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=1)])'
Estimating power demand for 'ElecMeter(instance=4, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=2)])'
Estimating power demand for 'ElecMeter(instance=5, building=11, dataset='WikiEnergy', appliances=[Appliance(type='dish washer', instance=1)])'
Estimating power demand for 'ElecMeter(instance=6, building=11, dataset='WikiEnergy', appliances=[Appliance(type='spin dryer', instance=1)])'
Estimating power demand for 'ElecMeter(instance=7, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric furnace', instance=1)])'
Estimating power demand for 'ElecMeter(instance=8, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=3)])'
Estimating power demand for 'ElecMeter(instance=9, building=11, dataset='WikiEnergy', appliances=[Appliance(type='sockets', instance=4)])'
Estimating power demand for 'ElecMeter(instance=10, building=11, dataset='WikiEnergy', appliances=[Appliance(type='fridge', instance=1)])'
Estimating power demand for 'ElecMeter(instance=11, building=11, dataset='WikiEnergy', appliances=[Appliance(type='electric water heating appliance', instance=1)])'

Examine disaggregated data

In [27]:
disag = DataSet(disag_filename)
disag_elec = disag.buildings[building_number].elec
disag_elec.plot()
plt.xlabel("Time")
disag.store.close()

Calculate accuracy of disaggregation

In [28]:
disag = DataSet(disag_filename)
disag_elec = disag.buildings[building_number].elec

f1 = f1_score(disag_elec, elec)
f1.index = disag_elec.get_labels(f1.index)
f1.plot(kind='barh', figsize=(10,6))
plt.ylabel('appliance');
plt.xlabel('f-score');

disag.store.close()

Note

This notebook was demonstrated at Buildsys 2014 (Nov. 2014), where it won the best demonstration award. Since then, the API has changed a bit and thus this notebook has been updated to reflect those changes.

In [29]:
# CSS styling
from IPython.core.display import display, HTML
display(HTML(open('static/styles.css', 'r').read()));