diff --git a/analyzer/__init__.py b/analyzer/__init__.py deleted file mode 100644 index 71f8bde..0000000 --- a/analyzer/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .analyzer import * -from .biogames import * -from .locomotion_action import * -from .mask import * diff --git a/analyzers/__init__.py b/analyzers/__init__.py new file mode 100644 index 0000000..377a221 --- /dev/null +++ b/analyzers/__init__.py @@ -0,0 +1,24 @@ +from .analyzer import Analyzer, Result +from .analyzer.biogames import BoardDurationAnalyzer +from .analyzer.default import LogEntryCountAnalyzer, LocationAnalyzer, LogEntrySequenceAnalyzer, ActionSequenceAnalyzer +from .analyzer.locomotion import LocomotionActionAnalyzer, CacheSequenceAnalyzer +from .analyzer.mask import MaskSpatials +from .render import Render +from .render.default import PrintRender +from .render.locomotion import LocomotionActionRelativeRender, LocomotionActionAbsoluteRender, \ + LocomotionActionRatioRender + +__FALLBACK__ = PrintRender +__MAPPING__ = { + LocomotionActionAnalyzer: [ + LocomotionActionAbsoluteRender, + LocomotionActionRelativeRender, + LocomotionActionRatioRender], + +} + + +def get_renderer(cls: type) -> [type]: + if cls not in __MAPPING__: + return [__FALLBACK__] + return __MAPPING__[cls] diff --git a/analyzers/analyzer/__init__.py b/analyzers/analyzer/__init__.py new file mode 100644 index 0000000..b8d8c60 --- /dev/null +++ b/analyzers/analyzer/__init__.py @@ -0,0 +1,30 @@ +from analyzers.settings import LogSettings + + +class Result: + def __init__(self, analysis, result): + self.result = result + self.__analysis__ = analysis + + def analysis(self): + return self.__analysis__ + + def get(self): + return self.result + + def __repr__(self): + return "" + + +class Analyzer: + def __init__(self, settings: LogSettings): + self.settings = settings + + def process(self, entry: dict) -> bool: + raise NotImplementedError() + + def result(self) -> Result: + raise NotImplementedError() + + def name(self): + return self.__name__ diff --git a/analyzer/biogames.py b/analyzers/analyzer/biogames.py similarity index 86% rename from analyzer/biogames.py rename to analyzers/analyzer/biogames.py index 7c6d213..49ca069 100644 --- a/analyzer/biogames.py +++ b/analyzers/analyzer/biogames.py @@ -1,8 +1,4 @@ -from collections import defaultdict - -from log_analyzer import LogSettings - -from .analyzer import Analyzer +from . import Result, LogSettings, Analyzer class BoardDurationAnalyzer(Analyzer): @@ -12,9 +8,9 @@ class BoardDurationAnalyzer(Analyzer): __name__ = "BoardDuration" def render(self) -> str: - return "\n".join(["{}\t{}".format(entry["active"], entry["id"]) for entry in self.result()]) + return "\n".join(["{}\t{}".format(entry["active"], entry["id"]) for entry in self.result().get()]) - def result(self) -> list: + def result(self) -> Result: result = [] last_timestamp = None last_board = None @@ -26,7 +22,7 @@ class BoardDurationAnalyzer(Analyzer): last_timestamp = timestamp last_board = board_id # TODO: last board? - return result + return Result(type(self), result) def process(self, entry: dict) -> bool: entry_type = entry[self.settings.type_field] diff --git a/analyzer/analyzer.py b/analyzers/analyzer/default.py similarity index 76% rename from analyzer/analyzer.py rename to analyzers/analyzer/default.py index 5f66b1e..163d660 100644 --- a/analyzer/analyzer.py +++ b/analyzers/analyzer/default.py @@ -1,21 +1,7 @@ import json -from collections import defaultdict, Iterable +from collections import defaultdict -from log_analyzer import LogSettings - - -class Analyzer: - def __init__(self, settings: LogSettings): - self.settings = settings - - def process(self, entry: dict) -> bool: - raise NotImplementedError() - - def result(self) -> Iterable: - raise NotImplementedError() - - def name(self): - return self.__name__ +from . import Result, LogSettings, Analyzer class LocationAnalyzer(Analyzer): @@ -31,8 +17,9 @@ class LocationAnalyzer(Analyzer): def __init__(self, settings: LogSettings): super().__init__(settings) - def result(self) -> list: - return self.entries + def result(self) -> Result: + # return self.entries + return Result(type(self), self.entries) def render(self, format: int = Formats.geojson): if format is self.Formats.geojson: @@ -51,8 +38,8 @@ class LogEntryCountAnalyzer(Analyzer): """ __name__ = "LogEntryCount" - def result(self) -> dict: - return dict(self.store) + def result(self) -> Result: + return Result(type(self), dict(self.store)) def process(self, entry: dict) -> bool: self.store[entry[self.settings.type_field]] += 1 @@ -69,8 +56,8 @@ class LogEntrySequenceAnalyzer(Analyzer): """ __name__ = "LogEntrySequence" - def result(self) -> list: - return self.store + def result(self) -> Result: + return Result(type(self), self.store) def process(self, entry: dict) -> bool: entry_type = entry[self.settings.type_field] diff --git a/analyzer/locomotion_action.py b/analyzers/analyzer/locomotion.py similarity index 71% rename from analyzer/locomotion_action.py rename to analyzers/analyzer/locomotion.py index fd64b7b..388a451 100644 --- a/analyzer/locomotion_action.py +++ b/analyzers/analyzer/locomotion.py @@ -1,9 +1,5 @@ -from collections import defaultdict - -from log_analyzer import LogSettings - -from .analyzer import Analyzer -from util import combinate +import util +from . import Analyzer, LogSettings, Result def init_filter(settings: LogSettings, state: str) -> callable: @@ -11,7 +7,7 @@ def init_filter(settings: LogSettings, state: str) -> callable: if type(settings.sequences[state]) in (str, list): return lambda entry: entry[settings.type_field] in settings.sequences[state] else: - return lambda entry: combinate(settings.sequences[state], entry) + return lambda entry: util.combinate(settings.sequences[state], entry) class LocomotionActionAnalyzer(Analyzer): @@ -45,16 +41,17 @@ class LocomotionActionAnalyzer(Analyzer): self.current_cache = None self.last = None - def result(self) -> dict: + def result(self) -> Result: if self.last is not None: if self.current_cache is None: self.locomotion.append(self.last - self.cache_time) else: self.actions.append(self.last - self.cache_time) + self.last = None locomotion = sum(self.locomotion) action = sum(self.actions) total = locomotion + action - return { + return Result(type(self), { 'locomotion_sum': locomotion, 'action_sum': action, 'locomotion': self.locomotion, @@ -63,32 +60,7 @@ class LocomotionActionAnalyzer(Analyzer): 'locomotion_relative': locomotion / total, 'action_relative': action / total, 'locomotion_action_ratio': locomotion / action, - } - - def render(self): - raw = self.result() - return [ - raw['locomotion_sum'], - raw['action_sum'], - raw['locomotion_relative'], - raw['action_relative'], - raw['locomotion_action_ratio'] - ] - - import numpy as np - import matplotlib.pyplot as plt - ind = np.arange(1) - loc = plt.bar(ind, [raw["locomotion_relative"]], 0.35) - act = plt.bar(ind, [raw["action_relative"]], 0.35) - #ratio = plt.plot([1,2,3],[raw['locomotion_action_ratio'],raw['locomotion_relative'],raw['action_relative']], label="ratio", marker=".") - ratio = plt.plot(ind,[raw['locomotion_action_ratio']], label="ratio", marker=".") - plt.ylabel("time") - plt.title("abs locomotion/action") - plt.xlabel("sessions") - - plt.xticks(ind, ["s1"]) - plt.legend((loc[0], act[0]), ("loc", "act")) - plt.show() + }) def __init__(self, settings: LogSettings): super().__init__(settings) @@ -114,8 +86,8 @@ class CacheSequenceAnalyzer(Analyzer): self.store.append((entry['timestamp'], entry['cache'])) return False - def result(self) -> list: - return self.store + def result(self) -> Result: + return Result(type(self), self.store) def __init__(self, settings: LogSettings): super().__init__(settings) diff --git a/analyzer/mask.py b/analyzers/analyzer/mask.py similarity index 83% rename from analyzer/mask.py rename to analyzers/analyzer/mask.py index 66038f1..b4fc310 100644 --- a/analyzer/mask.py +++ b/analyzers/analyzer/mask.py @@ -1,4 +1,4 @@ -from .analyzer import Analyzer +from . import Analyzer class MaskSpatials(Analyzer): @@ -12,4 +12,4 @@ class MaskSpatials(Analyzer): return False def result(self) -> int: - return self.masked \ No newline at end of file + return self.masked diff --git a/analyzers/render/__init__.py b/analyzers/render/__init__.py new file mode 100644 index 0000000..26c46d8 --- /dev/null +++ b/analyzers/render/__init__.py @@ -0,0 +1,6 @@ +from typing import List +from .. import Result + +class Render: + def render(self, results: List[Result]): + raise NotImplementedError() diff --git a/analyzers/render/default.py b/analyzers/render/default.py new file mode 100644 index 0000000..e480132 --- /dev/null +++ b/analyzers/render/default.py @@ -0,0 +1,8 @@ +from typing import List + +from . import Render + + +class PrintRender(Render): + def render(self, results: List): + print("\t" + "\n\t".join([str(r) for r in results])) diff --git a/analyzers/render/locomotion.py b/analyzers/render/locomotion.py new file mode 100644 index 0000000..6d11af2 --- /dev/null +++ b/analyzers/render/locomotion.py @@ -0,0 +1,64 @@ +from typing import List + +import matplotlib.pyplot as plt +import numpy as np + +from . import Render +from .. import Result + + +def plot(results: [[int]], ylabel: str, title: str, legend: (str,) = ("Locomotion", "Action")): + size = len(results) + data = list(zip(*results)) + ind = np.arange(size) + width = 0.85 + loc = plt.bar(ind, data[0], width=width, color="red") + act = plt.bar(ind, data[1], width=width, bottom=data[0], color="green") + # ratio = plt.plot([1,2,3],[raw['locomotion_action_ratio'],raw['locomotion_relative'],raw['action_relative']], label="ratio", marker=".") + # ratio = plt.plot(ind, data[4], label="ratio", marker=".") + plt.ylabel(ylabel) + plt.title(title) + plt.xlabel("sessions") + + # plt.xticks(ind, log_ids) + plt.xticks(ind, [""] * size) + # plt.yticks(np.arange(0,1.1,0.10)) + plt.legend((loc[0], act[0]), legend) + plt.show() + + +def plot_line(results: [[int]], ylabel="Ratio", title="Locomotion/Action "): + size = len(results) + data = list(zip(*results)) + ind = np.arange(size) + ratio = plt.plot(ind, data[0], label="ratio", marker=".") + plt.ylabel(ylabel) + plt.title(title) + plt.xticks(ind, [""] * size) + plt.show() + + +def filter_results(raw_results: [Result], keys) -> [[int]]: + results = [] + for result in raw_results: + raw = result.get() + results.append([raw[k] for k in keys]) + return results + + +class LocomotionActionAbsoluteRender(Render): + def render(self, results: List[Result]): + results = filter_results(results, ['locomotion_sum', 'action_sum']) + plot(results, "time", "abs loc/action") + + +class LocomotionActionRelativeRender(Render): + def render(self, results: List[Result]): + results = filter_results(results, ['locomotion_relative', 'action_relative']) + plot(results, "fraction of time", "rel loc/action") + + +class LocomotionActionRatioRender(Render): + def render(self, results: List[Result]): + results = filter_results(results, ['locomotion_action_ratio']) + plot_line(results, ylabel="Ratio", title="Locomotion/Action Ratio") diff --git a/analyzers/settings.py b/analyzers/settings.py new file mode 100644 index 0000000..88cb135 --- /dev/null +++ b/analyzers/settings.py @@ -0,0 +1,39 @@ +import json +import sys + + +class LogSettings: + log_format = None + type_field = None + spatials = None + actions = None + analyzers = [] + boards = None + sequences = None + + def __init__(self, json_dict): + self.log_format = json_dict['logFormat'] + self.type_field = json_dict['entryType'] + self.spatials = json_dict['spatials'] + self.actions = json_dict['actions'] + self.boards = json_dict['boards'] + for mod in json_dict['analyzers']: + for name in json_dict['analyzers'][mod]: + print(mod, name) + self.analyzers.append(getattr(sys.modules[mod], name)) + self.sequences = json_dict['sequences'] + + def __repr__(self): + return str({ + "logFormat": self.log_format, + "entryType": self.type_field, + "spatials": self.spatials, + "actions": self.actions, + "analyzers": self.analyzers, + "boards": self.boards, + "sequences": self.sequences, + }) + + +def load_settings(file: str) -> LogSettings: + return LogSettings(json.load(open(file))) diff --git a/biogames2.json b/biogames2.json index e1f3f3f..aad69dc 100644 --- a/biogames2.json +++ b/biogames2.json @@ -12,16 +12,12 @@ "de.findevielfalt.games.game2.instance.log.entry.ShowBoardLogEntry" ], "analyzers": { - "analyzer": [ + "analyzers": [ "LocationAnalyzer", "LogEntryCountAnalyzer", "LogEntrySequenceAnalyzer", - "ActionSequenceAnalyzer" - ], - "analyzer.biogames": [ - "BoardDurationAnalyzer" - ], - "analyzer.locomotion_action": [ + "ActionSequenceAnalyzer", + "BoardDurationAnalyzer", "LocomotionActionAnalyzer", "CacheSequenceAnalyzer" ] diff --git a/log_analyzer.py b/log_analyzer.py index 5d7066b..f0c6ef6 100644 --- a/log_analyzer.py +++ b/log_analyzer.py @@ -1,47 +1,11 @@ -import json -import sys from load import LOADERS -import analyzer from typing import List +from analyzers import get_renderer, Analyzer +from analyzers.settings import LogSettings, load_settings +import analyzers -class LogSettings: - log_format = None - type_field = None - spatials = None - actions = None - analyzers = [] - boards = None - sequences = None - - def __init__(self, json_dict): - self.log_format = json_dict['logFormat'] - self.type_field = json_dict['entryType'] - self.spatials = json_dict['spatials'] - self.actions = json_dict['actions'] - self.boards = json_dict['boards'] - for mod in json_dict['analyzers']: - for name in json_dict['analyzers'][mod]: - self.analyzers.append(getattr(sys.modules[mod], name)) - self.sequences = json_dict['sequences'] - - def __repr__(self): - return str({ - "logFormat": self.log_format, - "entryType": self.type_field, - "spatials": self.spatials, - "actions": self.actions, - "analyzers": self.analyzers, - "boards": self.boards, - "sequences": self.sequences, - }) - - -def load_settings(file: str) -> LogSettings: - return LogSettings(json.load(open(file))) - - -def process_log(log_id: str, settings: LogSettings) -> List: +def process_log(log_id: str, settings: LogSettings) -> List[Analyzer]: logfile = "data/inst_{id}/instance_log.sqlite".format(id=log_id) loader = LOADERS[settings.log_format]() try: @@ -60,44 +24,27 @@ def process_log(log_id: str, settings: LogSettings) -> List: if __name__ == '__main__': settings = load_settings("biogames2.json") - #print(settings) - log_id = "56d9b64144ab44e7b90bf766f3be32e3" - log_ids = ["56d9b64144ab44e7b90bf766f3be32e3","85a9ad58951e4fbda26f860c9b66f567"] + log_ids = ["56d9b64144ab44e7b90bf766f3be32e3", "85a9ad58951e4fbda26f860c9b66f567"] results = [] for log_id in log_ids: for analysis in process_log(log_id, settings): print("* Result for " + analysis.name()) - #print(analysis.result()) + # print(analysis.result()) + # print(analysis.render()) if analysis.name() in ("LocomotionAction"): - results.append(analysis.render()) + results.append(analysis.result()) + for r in get_renderer(analyzers.LocomotionActionAnalyzer): + r().render(results) +# for analyzers in analyzers: +# if analyzers.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]: +# print(json.dumps(analyzers.result(), indent=2)) - import numpy as np - import matplotlib.pyplot as plt - data = list(zip(*results)) - ind = np.arange(len(results)) - loc = plt.bar(ind, data[2], width=0.35, color="red") - act = plt.bar(ind, data[3], width=0.35, bottom=data[2], color="green") - # ratio = plt.plot([1,2,3],[raw['locomotion_action_ratio'],raw['locomotion_relative'],raw['action_relative']], label="ratio", marker=".") - #ratio = plt.plot(ind, data[4], label="ratio", marker=".") - plt.ylabel("time") - plt.title("abs locomotion/action") - plt.xlabel("sessions") +# for analyzers in analyzers: +# if analyzers.name() in ["BoardDuration"]: +# print(json.dumps(analyzers.result(), indent=2)) +# print(analyzers.render()) - #plt.xticks(ind, log_ids) - plt.xticks(ind, [""]*len(results)) - #plt.yticks(np.arange(0,1.1,0.10)) - plt.legend((loc[0], act[0]), ("loc", "act")) - plt.show() - # for analyzer in analyzers: - # if analyzer.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]: - # print(json.dumps(analyzer.result(), indent=2)) - - # for analyzer in analyzers: - # if analyzer.name() in ["BoardDuration"]: - # print(json.dumps(analyzer.result(), indent=2)) - # print(analyzer.render()) - - # coords = analyzers[1].render() - # with open("test.js", "w") as out: - # out.write("coords = "+coords) +# coords = analyzers[1].render() +# with open("test.js", "w") as out: +# out.write("coords = "+coords) diff --git a/requirements.txt b/requirements.txt index 4b43f7e..806f221 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ +numpy matplotlib \ No newline at end of file