Skip to content

Commit

Permalink
separate out activation_series_for_chunk and handle case where
Browse files Browse the repository at this point in the history
switch_off_events or switch_on_events is length 0
  • Loading branch information
JackKelly committed May 13, 2015
1 parent 3c4b9c9 commit 77a4e88
Showing 1 changed file with 89 additions and 50 deletions.
139 changes: 89 additions & 50 deletions nilmtk/electric.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,8 +701,8 @@ def plot_activity_histogram(self, ax=None, period='D', bin_duration='H',
ax.set_ylabel('Count')
return ax

def activation_series(self, min_off_duration=0, min_on_duration=0, border=1,
on_power_threshold=None, **kwargs):
def activation_series(self, min_off_duration=0, min_on_duration=0,
border=1, on_power_threshold=None, **kwargs):
"""Returns runs of an appliance.
Most appliances spend a lot of their time off. This function finds
Expand All @@ -712,11 +712,11 @@ def activation_series(self, min_off_duration=0, min_on_duration=0, border=1,
----------
min_off_duration : int
If min_off_duration > 0 then ignore 'off' periods less than
min_off_duration seconds of sub-threshold power consumption
min_off_duration seconds of sub-threshold power consumption
(e.g. a washing machine might draw no power for a short
period while the clothes soak.) Defaults to 0.
min_on_duration : int
Any activation lasting less seconds than min_on_duration will be
Any activation lasting less seconds than min_on_duration will be
ignored. Defaults to 0.
border : int
Number of rows to include before and after the detected activation
Expand All @@ -734,52 +734,10 @@ def activation_series(self, min_off_duration=0, min_on_duration=0, border=1,

activations = []
for chunk in self.power_series(**kwargs):
when_on = chunk >= on_power_threshold

# Find state changes
state_changes = when_on.astype(np.int8).diff()
del when_on
switch_on_events = np.where(state_changes == 1)[0]
switch_off_events = np.where(state_changes == -1)[0]
del state_changes

if len(switch_on_events) == 0 or len(switch_off_events) == 0:
continue

# Make sure events align
if switch_off_events[0] < switch_on_events[0]:
switch_off_events = switch_off_events[1:]
if switch_on_events[-1] > switch_off_events[-1]:
switch_on_events = switch_on_events[:-1]
assert len(switch_on_events) == len(switch_off_events)

# Smooth over off-durations less than min_off_duration
if min_off_duration > 0:
off_durations = (chunk.index[switch_on_events[1:]].values -
chunk.index[switch_off_events[:-1]].values)

off_durations = timedelta64_to_secs(off_durations)

above_threshold_off_durations = np.where(
off_durations >= min_off_duration)[0]

# Now remove off_events and on_events
switch_off_events = switch_off_events[
np.concatenate([above_threshold_off_durations,
[len(switch_off_events)-1]])]
switch_on_events = switch_on_events[
np.concatenate([[0], above_threshold_off_durations+1])]
assert len(switch_on_events) == len(switch_off_events)

for on, off in zip(switch_on_events, switch_off_events):
duration = (chunk.index[off] - chunk.index[on]).total_seconds()
if duration < min_on_duration:
continue
on -= 1 + border
if on < 0:
on = 0
off += border
activations.append(chunk.iloc[on:off])
activations_for_chunk = activation_series_for_chunk(
chunk, min_off_duration, min_on_duration,
border, on_power_threshold)
activations.extend(activations_for_chunk)

return activations

Expand Down Expand Up @@ -814,3 +772,84 @@ def align_two_meters(master, slave, func='power_series'):

yield pd.DataFrame({'master': master_chunk, 'slave': slave_chunk})


def activation_series_for_chunk(chunk, min_off_duration=0, min_on_duration=0,
border=1, on_power_threshold=5):
"""Returns runs of an appliance.
Most appliances spend a lot of their time off. This function finds
periods when the appliance is on.
Parameters
----------
chunk : pd.Series
min_off_duration : int
If min_off_duration > 0 then ignore 'off' periods less than
min_off_duration seconds of sub-threshold power consumption
(e.g. a washing machine might draw no power for a short
period while the clothes soak.) Defaults to 0.
min_on_duration : int
Any activation lasting less seconds than min_on_duration will be
ignored. Defaults to 0.
border : int
Number of rows to include before and after the detected activation
on_power_threshold : int or float
Defaults to self.on_power_threshold()
Returns
-------
list of pd.Series. Each series contains one activation.
"""
when_on = chunk >= on_power_threshold

# Find state changes
state_changes = when_on.astype(np.int8).diff()
del when_on
switch_on_events = np.where(state_changes == 1)[0]
switch_off_events = np.where(state_changes == -1)[0]
del state_changes

if len(switch_on_events) == 0 or len(switch_off_events) == 0:
return []

# Make sure events align
if switch_off_events[0] < switch_on_events[0]:
switch_off_events = switch_off_events[1:]
if len(switch_off_events) == 0:
return []
if switch_on_events[-1] > switch_off_events[-1]:
switch_on_events = switch_on_events[:-1]
if len(switch_on_events) == 0:
return []
assert len(switch_on_events) == len(switch_off_events)

# Smooth over off-durations less than min_off_duration
if min_off_duration > 0:
off_durations = (chunk.index[switch_on_events[1:]].values -
chunk.index[switch_off_events[:-1]].values)

off_durations = timedelta64_to_secs(off_durations)

above_threshold_off_durations = np.where(
off_durations >= min_off_duration)[0]

# Now remove off_events and on_events
switch_off_events = switch_off_events[
np.concatenate([above_threshold_off_durations,
[len(switch_off_events)-1]])]
switch_on_events = switch_on_events[
np.concatenate([[0], above_threshold_off_durations+1])]
assert len(switch_on_events) == len(switch_off_events)

activations = []
for on, off in zip(switch_on_events, switch_off_events):
duration = (chunk.index[off] - chunk.index[on]).total_seconds()
if duration < min_on_duration:
continue
on -= 1 + border
if on < 0:
on = 0
off += border
activations.append(chunk.iloc[on:off])

return activations

0 comments on commit 77a4e88

Please sign in to comment.