Skip to content

Commit

Permalink
Implemented ElecMeter.load() and MeterGroup.load(). #264
Browse files Browse the repository at this point in the history
  • Loading branch information
JackKelly committed Dec 10, 2014
1 parent b48e8b5 commit ff59303
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 82 deletions.
73 changes: 20 additions & 53 deletions nilmtk/elecmeter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .node import Node
from .electric import Electric
from .timeframe import TimeFrame, list_of_timeframe_dicts
from .exceptions import MeasurementError
import nilmtk

MAX_SIZE_ENTROPY = 10000
Expand Down Expand Up @@ -218,22 +219,8 @@ def available_ac_types(self, physical_quantity):

measurements = self.device['measurements']
return [m['type'] for m in measurements
if m['physical_quantity'] == physical_quantity]

def available_power_ac_types(self):
"""Finds available alternating current types from power measurements.
Returns
-------
list of strings e.g. ['apparent', 'active']
.. note:: Deprecated in NILMTK v0.3
`available_power_ac_types` should not be used. Instead please
use `available_ac_types('power').`
"""
warn("`available_power_ac_types` is deprecated. Please use"
" `available_ac_types('power')` instead.", DeprecationWarning)
return self.available_ac_types('power')
if m['physical_quantity'] == physical_quantity
and m.has_key('type')]

def available_physical_quantities(self):
"""
Expand Down Expand Up @@ -349,17 +336,18 @@ def load(self, **kwargs):
Returns
---------
Always return a generator of DataFrames (even if it only has a single columns).
Always return a generator of DataFrames (even if it only has a single
column).
"""

# Extract kwargs for this function
physical_quantities = kwargs.pop('physical_quantity', None)
ac_types = kwargs.pop('ac_type', None)
if (ac_types or physical_quantities) and kwargs.has_key('cols'):
raise ValueError("Cannot use `ac_types` and/or `physical_quantities`"
" with `cols` parameter.")
if ac_types or physical_quantities:
if kwargs.has_key('cols'):
raise ValueError("Cannot use `ac_types` and/or `physical_quantities`"
" with `cols` parameter.")

if not kwargs.has_key('cols'):
if physical_quantities is None:
physical_quantities = self.available_physical_quantities()
elif isinstance(physical_quantities, basestring):
Expand All @@ -371,12 +359,23 @@ def load(self, **kwargs):
cols = []
for physical_quantity in physical_quantities:
available_ac_types = self.available_ac_types(physical_quantity)
if not available_ac_types:
# then this is probably a physical quantity like 'voltage'
cols.append((physical_quantity, ''))
continue

if ac_types is None:
ac_types = available_ac_types
elif ac_types == ['best']:
ac_types = [select_best_ac_type(available_ac_types)]

for ac_type in ac_types:
if ac_type not in available_ac_types:
error_msg = ("AC type '{}' not available."
" only {} are available"
.format(ac_type, available_ac_types))
raise MeasurementError(error_msg)

cols.append((physical_quantity, ac_type))

kwargs['cols'] = cols
Expand All @@ -394,38 +393,6 @@ def load(self, **kwargs):

return generator

def power_series(self, **kwargs):
"""Get power Series.
Parameters
----------
**kwargs :
Any other key word arguments are passed to self.load()
Returns
-------
generator of pd.Series of power measurements.
.. note:: Deprecated in NILMTK v0.3
`power_series` should not be used. Instead, please use
`load` instead because it is more general purpose.
"""
warn("`power_series` should not be used and is deprecated in"
" NILMTK v0.3. Instead, please use `load` instead because it is"
" more general purpose.", DeprecationWarning)

# Select power column:
kwargs['physical_quantity'] = 'power'
kwargs['ac_type'] = 'best'

# Pull data through preprocessing pipeline
generator = self.load(**kwargs)
for chunk in generator:
chunk_to_yield = chunk.icol(0).dropna()
chunk_to_yield.timeframe = getattr(chunk, 'timeframe', None)
chunk_to_yield.look_ahead = getattr(chunk, 'look_ahead', None)
yield chunk_to_yield

def dry_run_metadata(self):
return self.metadata

Expand Down
48 changes: 47 additions & 1 deletion nilmtk/electric.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,6 @@ def plot_autocorrelation(self):
autocorrelation_plot(power, ax = ax)
return ax


def switch_times(self, threshold=40):
"""
Returns an array of pd.DateTime when a switch occurs as defined by threshold
Expand Down Expand Up @@ -450,6 +449,53 @@ def avgdigamma(points,dvec):
out.append(kdtree_mi(power_x_val, power_y_val, k, base))
return sum(out)/len(out)

def available_power_ac_types(self):
"""Finds available alternating current types from power measurements.
Returns
-------
list of strings e.g. ['apparent', 'active']
.. note:: Deprecated in NILMTK v0.3
`available_power_ac_types` should not be used. Instead please
use `available_ac_types('power').`
"""
warn("`available_power_ac_types` is deprecated. Please use"
" `available_ac_types('power')` instead.", DeprecationWarning)
return self.available_ac_types('power')

def power_series(self, **kwargs):
"""Get power Series.
Parameters
----------
**kwargs :
Any other key word arguments are passed to self.load()
Returns
-------
generator of pd.Series of power measurements.
.. note:: Deprecated in NILMTK v0.3
`power_series` should not be used. Instead, please use
`load` instead because it is more general purpose.
"""
warn("`power_series` should not be used and is deprecated in"
" NILMTK v0.3. Instead, please use `load` instead because it is"
" more general purpose.", DeprecationWarning)

# Select power column:
kwargs['physical_quantity'] = 'power'
kwargs['ac_type'] = 'best'

# Pull data through preprocessing pipeline
generator = self.load(**kwargs)
for chunk in generator:
chunk_to_yield = chunk.icol(0).dropna()
chunk_to_yield.timeframe = getattr(chunk, 'timeframe', None)
chunk_to_yield.look_ahead = getattr(chunk, 'look_ahead', None)
yield chunk_to_yield


# def activity_distribution(self):
# * activity distribution:
Expand Down
3 changes: 3 additions & 0 deletions nilmtk/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ class TooFewSamplesError(Exception):

class PerformanceWarning(RuntimeWarning):
pass

class MeasurementError(Exception):
pass
101 changes: 73 additions & 28 deletions nilmtk/metergroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,49 +514,74 @@ def draw_wiring_graph(self):
labels[meter] = meter_instances
nx.draw(graph, labels=labels)

def power_series(self, **kwargs):
"""Sum together all meters and return power Series.
def load(self, **kwargs):
"""Returns a generator of DataFrames loaded from the DataStore.
Parameters
----------
measurement_ac_type_prefs : list of strings, optional
if provided then will try to select the best AC type from
self.available_ac_types which is also in measurement_ac_type_prefs.
If none of the measurements from measurement_ac_type_prefs are
available then will raise a warning and will select another ac type.
By default, `load` will load all available columns from the DataStore.
Specific columns can be selected in one or two mutually exclusive ways:
1. specify a list of column names using the `cols` parameter.
2. specify a `physical_quantity` and/or an `ac_type` parameter to ask
`load` to automatically select columns.
See ElecMeter.load() docs for more parameters.
Parameters
---------------
physical_quantities : string or list of strings
e.g. 'power' or 'voltage' or 'energy' or ['power', 'energy'].
If a single string then load columns only for that physical quantity.
If a list of strings then load columns for all those physical
quantities.
ac_types : string or list of strings, defaults to None
Where 'ac_type' is short for 'alternating current type'. e.g.
'reactive' or 'active' or 'apparent'.
If set to None then will load all AC types per physical quantity.
If set to 'best' then load the single best AC type per
physical quantity.
If set to a single AC type then load just that single AC type per
physical quantity, else raise an Exception.
If set to a list of AC type strings then will load all those
AC types and will raise an Exception if any cannot be found.
cols : list of tuples, using NILMTK's vocabulary for measurements.
e.g. [('power', 'active'), ('voltage', ''), ('energy', 'reactive')]
`cols` can't be used if `ac_type` and/or `physical_quantity` are set.
preprocessing : list of Node subclass instances
e.g. [Clip()]
**kwargs : any other key word arguments to pass to `self.store.load()`
Returns
-------
generator of pd.Series of power measurements.
---------
Always return a generator of DataFrames (even if it only has a single
column).
Note
----
If meters do not align then resample to get multiple meters to align.
"""
.. note:: If meters do not align then resample first by passing in
`preprocessing=[Apply(func=lambda df:
pd.DataFrame.resample(df, rule='T', fill_method='ffill')]`
.. note:: Different AC types will be treated separately.
"""
# Get a list of generators
generators = []
for meter in self.meters:
generators.append(meter.power_series(**kwargs))
generators = [meter.load(**kwargs) for meter in self.meters]

# Now load each generator and yield the sum
# Load each generator and yield the sum
while True:
chunk = None
for generator in generators:
try:
another_chunk = next(generator)
chunk_from_next_meter = next(generator)
except StopIteration:
pass
else:
if chunk is None:
chunk = another_chunk
chunk = chunk_from_next_meter
timeframe = chunk.timeframe
else:
n = len(chunk)
timeframe = timeframe.intersect(another_chunk.timeframe)
chunk += another_chunk
timeframe = timeframe.intersect(chunk_from_next_meter.timeframe)
chunk += chunk_from_next_meter
chunk = chunk.dropna()
if len(chunk) < n:
warn("Meters are not perfectly aligned.")
Expand Down Expand Up @@ -929,11 +954,31 @@ def proportion_of_energy_submetered(self, **loader_kwargs):

return proportion

def available_power_ac_types(self):
"""Returns set of all AC types recorded by all meters"""
all_ac_types = [meter.available_power_ac_types()
def available_ac_types(self, physical_quantity):
"""Returns set of all available alternating current types for a
specific physical quantity.
Parameters
----------
physical_quantity : str
Returns
-------
list of strings e.g. ['apparent', 'active']
"""
all_ac_types = [meter.available_ac_types(physical_quantity)
for meter in self.meters]
return set(flatten_2d_list(all_ac_types))
return list(set(flatten_2d_list(all_ac_types)))

def available_physical_quantities(self):
"""
Returns
-------
list of strings e.g. ['power', 'energy']
"""
all_physical_quants = [meter.available_physical_quantities()
for meter in self.meters]
return list(set(flatten_2d_list(all_physical_quants)))

def energy_per_meter(self, **load_kwargs):
"""Returns pd.DataFrame where columns is meter.identifier and
Expand Down

0 comments on commit ff59303

Please sign in to comment.