All NILM datasets consists of various groupings of electricity meters. We can group the meters by house. Or by the type of appliance they are directly connected to. Or by sample rate. Or by whether the meter is a whole-house "site meter" or an appliance-level submeter, or a circuit-level submeter.
In NILMTK, one of the key classes is MeterGroup
which stores a list of meters
and allows us to select a subset of meters, aggregate power from all meters and many other functions.
When we first open a DataSet
, NILMTK creates several MeterGroup
objects. There's nilmtk.global_meter_group
which holds every meter currently loaded (including from multiple datasets if you have opened more than one dataset). There is also one MeterGroup
per building (which live in the Building.elec
attribute). We also nested MeterGroups
for aggregating together split-phase mains, 3-phase mains and dual-supply (240 volt) appliances in North American and Canadan datasets. For example, here is the MeterGroup
for building 1 in REDD:
In [20]:
from matplotlib import rcParams
import matplotlib.pyplot as plt
%matplotlib inline
rcParams['figure.figsize'] = (13, 6)
plt.style.use('ggplot')
from nilmtk import DataSet
redd = DataSet('/data/REDD/redd.h5')
elec = redd.buildings[1].elec
elec
Out[20]:
Note that there are two nested MeterGroups
: one for the electric oven, and one for the washer dryer (both of which are 240 volt appliances and have two meters per appliance):
In [2]:
elec.nested_metergroups()
Out[2]:
Putting these meters into a MeterGroup
allows us to easily sum together the power demand recorded by both meters to get the total power demand for the entire appliance (but it's also very easy to see the individual meter power demand too).
We can easily get a MeterGroup of either the submeters or the mains:
In [3]:
elec.mains()
Out[3]:
We can easily get the power data for both mains meters summed together:
In [4]:
elec.mains().power_series_all_data().head()
Out[4]:
In [5]:
elec.submeters()
Out[5]:
Let's work out the proportion of energy submetered in REDD building 1:
In [6]:
elec.proportion_of_energy_submetered()
Out[6]:
Note that NILMTK has raised a warning that Mains uses a different type of power measurement than all the submeters, so it's not an entirely accurate comparison. Which raises the question: which type of power measurements are used for the mains and submeters? Let's find out...
In [9]:
mains = elec.mains()
In [10]:
mains.available_power_ac_types()
Out[10]:
In [11]:
elec.submeters().available_power_ac_types()
Out[11]:
In [ ]:
elec.load()
In [12]:
elec.mains().total_energy() # returns kWh
Out[12]:
In [13]:
energy_per_meter = elec.submeters().energy_per_meter() # kWh, again
energy_per_meter
Out[13]:
column headings are the ElecMeter instance numbers.
The function fraction_per_meter
does the same thing as energy_per_submeter
but returns the fraction of energy per meter.
Let's make a new MeterGroup which only contains the ElecMeters which used more than 20 kWh:
In [14]:
# energy_per_meter is a DataFrame where each row is a
# power type ('active', 'reactive' or 'apparent').
# All appliance meters in REDD are record 'active' so just select
# the 'active' row:
energy_per_meter = energy_per_meter.loc['active']
more_than_20 = energy_per_meter[energy_per_meter > 20]
more_than_20
Out[14]:
In [15]:
instances = more_than_20.index
instances
Out[15]:
We can get the wiring diagram for the MeterGroup:
In [21]:
elec.draw_wiring_graph()
Out[21]:
It's not very pretty but it shows that meters (1,2) (the site meters) are upstream of all other meters.
Buildings in REDD have only two levels in their meter hierarchy (mains and submeters). If there were more than two levels then it might be useful to get only the meters immediately downstream of mains:
In [22]:
elec.meters_directly_downstream_of_mains()
Out[22]:
The ElecMeter
class represents a single electricity meter. Each ElecMeter
has a list of associated Appliance
objects. ElecMeter
has many of the same stats methods as MeterGroup
such as total_energy
and available_power_ac_types
and power_series
and power_series_all_data
. We will now explore some more stats functions (many of which are also available on MeterGroup
)...
In [23]:
fridge_meter = elec['fridge']
In [24]:
fridge_meter.upstream_meter() # happens to be the mains meter group!
Out[24]:
In [25]:
fridge_meter.device
Out[25]:
If the metadata specifies that a meter has multiple meters connected to it then one of those can be specified as the 'dominant' appliance, and this appliance can be retrieved with this method:
In [26]:
fridge_meter.dominant_appliance()
Out[26]:
In [27]:
fridge_meter.total_energy() # kWh
Out[27]:
If we plot the raw power data then we see there is one large gap where, supposedly, the metering system was not working. (if we were to zoom in then we'd see lots of smaller gaps too):
In [28]:
fridge_meter.plot()
Out[28]:
We can automatically identify the 'good sections' (i.e. the sections where every pair of consecutive samples is less than max_sample_period
specified in the dataset metadata):
In [29]:
good_sections = fridge_meter.good_sections(full_results=True)
# specifying full_results=False would give us a simple list of
# TimeFrames. But we want the full GoodSectionsResults object so we can
# plot the good sections...
In [30]:
good_sections.plot()
Out[30]:
The blue chunks show where the data is good. The white gap is the large gap seen in the raw power data. There are lots of smaller gaps that we cannot see at this zoom level.
We can also see the exact sections identified:
In [31]:
good_sections.combined()
Out[31]:
As well as large gaps appearing because the entire system is down, we also get frequent small gaps from wireless sensors dropping data. This is sometimes called 'dropout'. The dropout rate is a number between 0 and 1 which specifies the proportion of missing samples. A dropout rate of 0 means no samples are missing. A value of 1 would mean all samples are missing:
In [32]:
fridge_meter.dropout_rate()
Out[32]:
Note that the dropout rate has gone down (which is good!) now that we are ignoring the gaps. This value is probably more representative of the performance of the wireless system.
We use ElecMeter.select_using_appliances()
to select a new MeterGroup using an metadata field. For example, to get all the washer dryers in the whole of the REDD dataset:
In [34]:
import nilmtk
nilmtk.global_meter_group.select_using_appliances(type='washer dryer')
Out[34]:
Or all appliances in the 'heating' category:
In [35]:
nilmtk.global_meter_group.select_using_appliances(category='heating')
Out[35]:
Or all appliances in building 1 with a single-phase induction motor(!):
In [36]:
nilmtk.global_meter_group.select_using_appliances(building=1, category='single-phase induction motor')
Out[36]:
(NILMTK imports the 'common metadata' from the NILM Metadata project, which includes a wide range of different category taxonomies)
In [37]:
nilmtk.global_meter_group.select_using_appliances(building=2, category='laundry appliances')
Out[37]:
In [38]:
elec.select(device_model='REDD_whole_house')
Out[38]:
In [39]:
elec.select(sample_period=3)
Out[39]:
We use []
to retrive a single ElecMeter
from a MeterGroup
.
In [40]:
elec['fridge']
Out[40]:
Appliances are uniquely identified within a building by a type
(fridge, kettle, television, etc.) and an instance
number. If we do not specify an instance number then ElecMeter
retrieves instance 1 (instance numbering starts from 1). If you want a different instance then just do this:
In [41]:
elec['light', 2]
Out[41]:
To uniquely identify an appliance in nilmtk.global_meter_group
then we must specify the dataset name, building instance number, appliance type and appliance instance in a dict:
In [42]:
import nilmtk
nilmtk.global_meter_group[{'dataset': 'REDD', 'building': 1, 'type': 'fridge', 'instance': 1}]
Out[42]:
get ElecMeter with instance = 1:
In [43]:
elec[1]
Out[43]:
ElecMeter and Appliance instance numbers uniquely identify the meter or appliance type within the building, not globally. To uniquely identify a meter globally, we need three keys:
In [44]:
from nilmtk.elecmeter import ElecMeterID
# ElecMeterID is a namedtuple for uniquely identifying each ElecMeter
nilmtk.global_meter_group[ElecMeterID(instance=8, building=1, dataset='REDD')]
Out[44]:
We can also select a single, existing nested MeterGroup. There are two ways to specify a nested MeterGroup:
In [45]:
elec[[ElecMeterID(instance=3, building=1, dataset='REDD'),
ElecMeterID(instance=4, building=1, dataset='REDD')]]
Out[45]:
In [46]:
elec[ElecMeterID(instance=(3,4), building=1, dataset='REDD')]
Out[46]:
We can also specify the mains by asking for meter instance 0:
In [47]:
elec[ElecMeterID(instance=0, building=1, dataset='REDD')]
Out[47]:
which is equivalent to elec.mains():
In [48]:
elec.mains() == elec[ElecMeterID(instance=0, building=1, dataset='REDD')]
Out[48]: