Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monthly windstats #172

Merged
merged 6 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions merlion/models/anomaly/windstats_monthly.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
logger = logging.getLogger(__name__)


class WindStatsConfig(DetectorConfig):
class MonthlyWindStatsConfig(DetectorConfig):
"""
Config class for `WindStats`.
"""
Expand Down Expand Up @@ -61,15 +61,15 @@ class MonthlyWindStats(DetectorBase):
minimum of the scores is returned.
"""

config_class = WindStatsConfig
config_class = MonthlyWindStatsConfig

def __init__(self, config: WindStatsConfig = None):
def __init__(self, config: MonthlyWindStatsConfig = None):
"""
config.wind_sz: the window size in minutes, default is 30 minute window
config.max_days: maximum number of days stored in memory (only mean and std of each window are stored), default is 4 days
here the days are first bucketized and then bucketized by window id.
"""
super().__init__(WindStatsConfig() if config is None else config)
super().__init__(MonthlyWindStatsConfig() if config is None else config)
self.table = {}

@property
Expand Down
67 changes: 50 additions & 17 deletions merlion/models/anomaly/windstats_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,83 @@
For the implementation of only weekly/monthly seasonality, specify "enable_weekly" of "enable_monthly" arguments of RunWindStats().
"""

from windstats import WindStats, WindStatsConfig
from windstats_monthly import MonthlyWindStats, MonthlyWindStatsConfig
from ts_datasets.anomaly import NAB
from merlion.models.anomaly.windstats import WindStats, WindStatsConfig
from merlion.models.anomaly.windstats_monthly import MonthlyWindStats, MonthlyWindStatsConfig
from merlion.utils import TimeSeries
from merlion.post_process.threshold import AggregateAlarms

class RunWindStats:
def __init__(self, threshold, enable_weekly = True, enable_monthly = True, WeeklyWindStatsConfig = WindStatsConfig(), MonthlyWindStatsConfig = MonthlyWindStatsConfig()):
def __init__(
self,
threshold,
enable_weekly = True,
enable_monthly = True,
post_rule_on_anom_score = False,
WeeklyWindStatsConfig = WindStatsConfig(),
MonthlyWindStatsConfig = MonthlyWindStatsConfig(),
return_score = True
):
"""
Users can customize the configuration for weekly or monthly-based windstats. If not, then the default configuration will apply.
"""

self.enable_weekly = enable_weekly
self.enable_monthly = enable_monthly
self.return_score = return_score
assert self.enable_weekly == True or self.enable_monthly == True, "Must enable either weekly or monthly seasonality, or both!"

# Threshold on identifying anomaly based on anomaly score.
self.threshold = threshold
# If apply post rules on anomaly score
self.post_rule = post_rule_on_anom_score

# Intialize according model if enable weekly/monthly analysis
if self.enable_weekly:
self.model_weekly = WindStats(WeeklyWindStatsConfig)

if self.enable_monthly:
self.model_monthly = MonthlyWindStats(MonthlyWindStatsConfig)

# Identify anomaly based on the hard threshold.
def anomalyByScore(self, scores, threshold):
scores.loc[abs(scores["anom_score"]) <= threshold] = 0
scores.loc[abs(scores["anom_score"]) > threshold] = 1
labels = scores.copy()
labels.loc[abs(labels["anom_score"]) <= threshold] = 0
labels.loc[abs(labels["anom_score"]) > threshold] = 1

scores.rename(columns = {"anom_score": "anomaly"}, inplace = True)
return scores
labels.rename(columns = {"anom_score": "anomaly"}, inplace = True)
return labels

# Filter anomaly scores based on post rules. Same as "get_anomaly_label" in WindStats
def get_anomaly_label(self, model, ts):
scores = model.train(ts)
return model.post_rule(scores) if model.post_rule is not None else scores

def run(self, ts):
if self.enable_weekly:
scores_weekly = self.model_weekly.train(ts).to_pd()
scores_weekly = self.anomalyByScore(scores_weekly, self.threshold)
if self.post_rule:
scores_weekly = self.get_anomaly_label(self.model_weekly, ts).to_pd()
else:
scores_weekly = self.model_weekly.train(ts).to_pd()
labels_weekly = self.anomalyByScore(scores_weekly, self.threshold)

if self.enable_monthly:
scores_monthly = self.model_monthly.train(ts).to_pd()
scores_monthly = self.anomalyByScore(scores_monthly, self.threshold)
if self.post_rule:
scores_monthly = self.get_anomaly_label(self.model_monthly, ts).to_pd()
else:
scores_monthly = self.model_monthly.train(ts).to_pd()
labels_monthly = self.anomalyByScore(scores_monthly, self.threshold)

# Anomaly is identified if and only if it's detected in both weekly and monthly patterns.
if self.enable_weekly and self.enable_monthly:
return scores_weekly * scores_monthly
if self.return_score:
return scores_weekly, scores_monthly, scores_weekly * scores_monthly
else:
return scores_weekly, scores_monthly, labels_weekly * labels_monthly
elif self.enable_weekly:
return scores_weekly
if self.return_score:
return scores_weekly, None, scores_weekly
else:
return scores_weekly, None, labels_weekly
else:
return scores_monthly
if self.return_score:
return None, scores_monthly, scores_monthly
else:
return None, scores_monthly, labels_monthly
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def read_file(fname):
"py4j",
"matplotlib",
"plotly>=4.13",
"numpy>=1.21", # 1.21 remediates a security risk
"numpy>=1.21,<2.0", # 1.21 remediates a security risk
"packaging",
"pandas>=1.1.0", # >=1.1.0 for origin kwarg to df.resample()
"prophet>=1.1", # 1.1 removes dependency on pystan
Expand Down
Loading