diff options
| author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-02-12 18:32:19 -0500 |
|---|---|---|
| committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-02-12 18:32:19 -0500 |
| commit | 4a1c47705868ce2c48f1c57a7190d435e16c3ce8 (patch) | |
| tree | 9d3540dc0203d1f5508ee2919c8d68bfff97a861 | |
| parent | b2fa65ecfe14bb9377fbd8afa5f457a07472b6fb (diff) | |
Optimized plot script to handle different directory structures.
| -rw-r--r-- | parse/point.py | 6 | ||||
| -rw-r--r-- | parse/tuple_table.py | 19 | ||||
| -rw-r--r-- | plot/__init__.py | 0 | ||||
| -rw-r--r-- | plot/style.py | 62 | ||||
| -rwxr-xr-x | plot_exps.py | 154 |
5 files changed, 137 insertions, 104 deletions
diff --git a/parse/point.py b/parse/point.py index ce9cfb0..d577306 100644 --- a/parse/point.py +++ b/parse/point.py | |||
| @@ -8,9 +8,9 @@ from enum import Enum | |||
| 8 | from collections import defaultdict | 8 | from collections import defaultdict |
| 9 | 9 | ||
| 10 | Type = Enum(['Min','Max','Avg','Var']) | 10 | Type = Enum(['Min','Max','Avg','Var']) |
| 11 | default_typemap = {Type.Max : {Type.Max : 1, Type.Min : 0, Type.Avg : 1, Type.Var : 1}, | 11 | default_typemap = {Type.Max : {Type.Max : 1, Type.Min : 1, Type.Avg : 1, Type.Var : 1}, |
| 12 | Type.Min : {Type.Max : 1, Type.Min : 0, Type.Avg : 1, Type.Var : 1}, | 12 | Type.Min : {Type.Max : 1, Type.Min : 1, Type.Avg : 1, Type.Var : 1}, |
| 13 | Type.Avg : {Type.Max : 1, Type.Min : 0, Type.Avg : 1, Type.Var : 1}} | 13 | Type.Avg : {Type.Max : 1, Type.Min : 1, Type.Avg : 1, Type.Var : 1}} |
| 14 | 14 | ||
| 15 | def make_typemap(): | 15 | def make_typemap(): |
| 16 | return copy.deepcopy(default_typemap) | 16 | return copy.deepcopy(default_typemap) |
diff --git a/parse/tuple_table.py b/parse/tuple_table.py index 105b786..86baa08 100644 --- a/parse/tuple_table.py +++ b/parse/tuple_table.py | |||
| @@ -27,6 +27,9 @@ class TupleTable(object): | |||
| 27 | key = self.col_map.get_key(kv) | 27 | key = self.col_map.get_key(kv) |
| 28 | return key in self.table | 28 | return key in self.table |
| 29 | 29 | ||
| 30 | def __iter__(self): | ||
| 31 | return self.table.iteritems() | ||
| 32 | |||
| 30 | def reduce(self): | 33 | def reduce(self): |
| 31 | reduced = ReducedTupleTable(self.col_map) | 34 | reduced = ReducedTupleTable(self.col_map) |
| 32 | for key, value in self.table.iteritems(): | 35 | for key, value in self.table.iteritems(): |
| @@ -92,21 +95,27 @@ class ReducedTupleTable(TupleTable): | |||
| 92 | def from_dir_map(dir_map): | 95 | def from_dir_map(dir_map): |
| 93 | Leaf = namedtuple('Leaf', ['stat', 'variable', 'base', | 96 | Leaf = namedtuple('Leaf', ['stat', 'variable', 'base', |
| 94 | 'summary', 'config', 'values']) | 97 | 'summary', 'config', 'values']) |
| 98 | |||
| 99 | def next_type(path): | ||
| 100 | return path.pop() if path[-1] in Type else Type.Avg | ||
| 101 | |||
| 95 | def leafs(): | 102 | def leafs(): |
| 96 | for path, node in dir_map.leafs(): | 103 | for path, node in dir_map.leafs(): |
| 97 | # The path will be of at least size 1: the filename | 104 | # The path will be of at least size 1: the filename |
| 98 | leaf = path.pop() | 105 | leaf = path.pop() |
| 99 | 106 | ||
| 100 | # Set acceptable defaults for the rest of the path | 107 | base = path.pop() if (path and path[-1] in Type) else Type.Avg |
| 101 | path += ['?', '?', 'Avg', 'Avg'][len(path):] | 108 | summ = path.pop() if (path and path[-1] in Type) else Type.Avg |
| 109 | |||
| 110 | path += ['?', '?'][len(path):] | ||
| 102 | 111 | ||
| 103 | [stat, variable, base_type, summary_type] = path | 112 | [stat, variable] = path |
| 104 | 113 | ||
| 105 | config_str = leaf[:leaf.index('.csv')] | 114 | config_str = leaf[:leaf.index('.csv')] |
| 106 | config = ColMap.decode(config_str) | 115 | config = ColMap.decode(config_str) |
| 107 | 116 | ||
| 108 | leaf = Leaf(stat, variable, base_type, | 117 | leaf = Leaf(stat, variable, base, summ, |
| 109 | summary_type, config, node.values) | 118 | config, node.values) |
| 110 | yield leaf | 119 | yield leaf |
| 111 | 120 | ||
| 112 | builder = ColMapBuilder() | 121 | builder = ColMapBuilder() |
diff --git a/plot/__init__.py b/plot/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plot/__init__.py | |||
diff --git a/plot/style.py b/plot/style.py new file mode 100644 index 0000000..7e964b0 --- /dev/null +++ b/plot/style.py | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | from collections import namedtuple | ||
| 2 | import matplotlib.pyplot as plot | ||
| 3 | |||
| 4 | class Style(namedtuple('SS', ['marker', 'line', 'color'])): | ||
| 5 | def fmt(self): | ||
| 6 | return self.marker + self.line + self.color | ||
| 7 | |||
| 8 | class StyleMap(object): | ||
| 9 | '''Maps configs (dicts) to specific line styles.''' | ||
| 10 | DEFAULT = Style('.', '-', 'k') | ||
| 11 | |||
| 12 | def __init__(self, col_list, col_values): | ||
| 13 | '''Assign (some) columns in @col_list to fields in @Style to vary, and | ||
| 14 | assign values for these columns to specific field values.''' | ||
| 15 | self.value_map = {} | ||
| 16 | self.field_map = {} | ||
| 17 | |||
| 18 | for field, values in self.__get_all()._asdict().iteritems(): | ||
| 19 | next_column = col_list.pop(0) | ||
| 20 | value_dict = {} | ||
| 21 | |||
| 22 | for value in sorted(col_values[next_column]): | ||
| 23 | value_dict[value] = values.pop(0) | ||
| 24 | |||
| 25 | self.value_map[next_column] = value_dict | ||
| 26 | self.field_map[next_column] = field | ||
| 27 | |||
| 28 | def __get_all(self): | ||
| 29 | '''A Style holding all possible values for each property.''' | ||
| 30 | return Style(list('.,ov^<>1234sp*hH+xDd|_'), # markers | ||
| 31 | ['-', ':', '--'], # lines | ||
| 32 | list('bgrcmyk')) # colors | ||
| 33 | |||
| 34 | def get_style(self, kv): | ||
| 35 | '''Translate column values to unique line style.''' | ||
| 36 | style_fields = {} | ||
| 37 | |||
| 38 | for column, values in self.value_map.iteritems(): | ||
| 39 | if column not in kv: | ||
| 40 | continue | ||
| 41 | field = self.field_map[column] | ||
| 42 | style_fields[field] = values[kv[column]] | ||
| 43 | |||
| 44 | return StyleMap.DEFAULT._replace(**style_fields) | ||
| 45 | |||
| 46 | def get_key(self): | ||
| 47 | '''A visual description of this StyleMap.''' | ||
| 48 | key = [] | ||
| 49 | |||
| 50 | for column, values in self.value_map.iteritems(): | ||
| 51 | # print("***%s, %s" % column, values) | ||
| 52 | for v in values.keys(): | ||
| 53 | sdict = dict([(column, v)]) | ||
| 54 | style = self.get_style(sdict) | ||
| 55 | |||
| 56 | styled_line = plot.plot([], [], style.fmt())[0] | ||
| 57 | description = "%s:%s" % (column, v) | ||
| 58 | |||
| 59 | key += [(styled_line, description)] | ||
| 60 | |||
| 61 | return sorted(key, key=lambda x:x[1]) | ||
| 62 | |||
diff --git a/plot_exps.py b/plot_exps.py index 39529bd..bb6a707 100755 --- a/plot_exps.py +++ b/plot_exps.py | |||
| @@ -1,15 +1,16 @@ | |||
| 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
| 2 | from __future__ import print_function | 2 | from __future__ import print_function |
| 3 | 3 | ||
| 4 | import matplotlib.pyplot as plot | ||
| 4 | import os | 5 | import os |
| 5 | import shutil as sh | 6 | import shutil as sh |
| 6 | import sys | 7 | import sys |
| 8 | from collections import namedtuple | ||
| 7 | from optparse import OptionParser | 9 | from optparse import OptionParser |
| 10 | from parse.col_map import ColMap | ||
| 8 | from parse.dir_map import DirMap | 11 | from parse.dir_map import DirMap |
| 9 | from parse.tuple_table import ReducedTupleTable | 12 | from parse.tuple_table import ReducedTupleTable |
| 10 | from parse.col_map import ColMap | 13 | from plot.style import StyleMap |
| 11 | from collections import namedtuple,defaultdict | ||
| 12 | import matplotlib.pyplot as plot | ||
| 13 | 14 | ||
| 14 | def parse_args(): | 15 | def parse_args(): |
| 15 | parser = OptionParser("usage: %prog [options] [csv_dir]...") | 16 | parser = OptionParser("usage: %prog [options] [csv_dir]...") |
| @@ -21,15 +22,16 @@ def parse_args(): | |||
| 21 | 22 | ||
| 22 | return parser.parse_args() | 23 | return parser.parse_args() |
| 23 | 24 | ||
| 24 | |||
| 25 | ExpDetails = namedtuple('ExpDetails', ['variable', 'value', 'title', 'out']) | 25 | ExpDetails = namedtuple('ExpDetails', ['variable', 'value', 'title', 'out']) |
| 26 | OUT_FORMAT = 'pdf' | 26 | OUT_FORMAT = 'pdf' |
| 27 | 27 | ||
| 28 | def get_details(path): | 28 | def get_details(path, out_dir): |
| 29 | '''Decode a @path into details about a single experiment.''' | ||
| 29 | out = "_".join(path) if path else "plot" | 30 | out = "_".join(path) if path else "plot" |
| 31 | out = "%s/%s.%s" % (out_dir, out, OUT_FORMAT) | ||
| 30 | 32 | ||
| 31 | value = path.pop() if path else None | 33 | value = path.pop(0) if path else None |
| 32 | variable = path.pop() if path else None | 34 | variable = path.pop(0) if path else None |
| 33 | 35 | ||
| 34 | title = value.capitalize() if value else "" | 36 | title = value.capitalize() if value else "" |
| 35 | title += " by %s" % variable if variable else "" | 37 | title += " by %s" % variable if variable else "" |
| @@ -37,105 +39,70 @@ def get_details(path): | |||
| 37 | 39 | ||
| 38 | return ExpDetails(variable, value, title, out) | 40 | return ExpDetails(variable, value, title, out) |
| 39 | 41 | ||
| 42 | def plot_by_variable(plot_node, col_map, details): | ||
| 43 | '''Plot each .csv files under @plot_node as a line on a shared plot.''' | ||
| 40 | 44 | ||
| 45 | # Generate mapping of (column)=>(line property to vary) for consistently | ||
| 46 | # formatted plots | ||
| 47 | columns = list(col_map.columns()) | ||
| 48 | if details.variable and details.variable in columns: | ||
| 49 | columns.remove(details.variable) | ||
| 50 | style_map = StyleMap(columns, col_map.get_values()) | ||
| 41 | 51 | ||
| 42 | class StyleMap(object): | 52 | figure = plot.figure() |
| 43 | COLORS = list('bgrcmyk') | 53 | axes = figure.add_subplot(111) |
| 44 | LINES = ['-', ':', '--'] | ||
| 45 | MARKERS = list('.,ov^<>1234sp*hH+xDd|_') | ||
| 46 | ORDER = [MARKERS, COLORS, LINES] | ||
| 47 | DEFAULT = ["k", "-", "k"] | ||
| 48 | |||
| 49 | def __init__(self, col_list, col_values): | ||
| 50 | self.prop_map = dict(zip(col_list, StyleMap.ORDER)) | ||
| 51 | |||
| 52 | # Store 1 style per value | ||
| 53 | self.value_map = defaultdict(dict) | ||
| 54 | for column, styles in self.prop_map.iteritems(): | ||
| 55 | value_styles = self.value_map[column] | ||
| 56 | for value in sorted(col_values[column]): | ||
| 57 | value_styles[value] = styles.pop(0) | ||
| 58 | styles += [value_styles[value]] | ||
| 59 | |||
| 60 | def get_style(self, kv): | ||
| 61 | style = '' | ||
| 62 | for k,v in kv.iteritems(): | ||
| 63 | if k in self.value_map: | ||
| 64 | style += self.value_map[k][v] | ||
| 65 | return style | ||
| 66 | |||
| 67 | def get_key(self): | ||
| 68 | key = [] | ||
| 69 | for column, properties in self.prop_map.iteritems(): | ||
| 70 | idx = StyleMap.ORDER.index(properties) | ||
| 71 | prop_string = StyleMap.DEFAULT[idx] + "%s" | ||
| 72 | for value, prop in self.value_map[column].iteritems(): | ||
| 73 | style = plot.plot([],[], prop_string%prop)[0] | ||
| 74 | key += [(style, "%s:%s" % (column, value))] | ||
| 75 | return sorted(key, key=lambda x:x[1]) | ||
| 76 | |||
| 77 | def plot_by_variable(dir_map, col_map, out_dir, force): | ||
| 78 | num_plots = 0 | ||
| 79 | id = 0 | ||
| 80 | for _,_ in dir_map.leafs(1): | ||
| 81 | num_plots += 1 | ||
| 82 | sys.stderr.write("Plotting by variable...") | ||
| 83 | |||
| 84 | for plot_path, plot_node in dir_map.leafs(1): | ||
| 85 | id += 1 | ||
| 86 | details = get_details(plot_path) | ||
| 87 | out_fname = "%s/%s.%s" % (out_dir, details.out, OUT_FORMAT) | ||
| 88 | if os.path.exists(out_fname) and not force: | ||
| 89 | continue | ||
| 90 | |||
| 91 | # Kinda bad... | ||
| 92 | first_csv = plot_node.children.keys()[0] | ||
| 93 | first_config = ColMap.decode(first_csv[:first_csv.index('.csv')]) | ||
| 94 | columns = filter(lambda c: c in first_config, col_map.columns()) | ||
| 95 | |||
| 96 | style_map = StyleMap(columns, col_map.get_values()) | ||
| 97 | |||
| 98 | figure = plot.figure() | ||
| 99 | axes = figure.add_subplot(111) | ||
| 100 | 54 | ||
| 101 | for line_path, line_node in plot_node.children.iteritems(): | 55 | # Create a line for each file node |
| 102 | encoded = line_path[:line_path.index(".csv")] | 56 | for line_path, line_node in plot_node.children.iteritems(): |
| 103 | config = ColMap.decode(encoded) | 57 | # Create line style to match this configuration |
| 104 | style = style_map.get_style(config) | 58 | encoded = line_path[:line_path.index(".csv")] |
| 59 | config = ColMap.decode(encoded) | ||
| 60 | style = style_map.get_style(config) | ||
| 105 | 61 | ||
| 106 | values = sorted(line_node.values, key=lambda tup: tup[0]) | 62 | values = sorted(line_node.values, key=lambda tup: tup[0]) |
| 107 | xvalues, yvalues = zip(*values) | 63 | xvalues, yvalues = zip(*values) |
| 108 | 64 | ||
| 109 | plot.plot(xvalues, yvalues, style) | 65 | plot.plot(xvalues, yvalues, style.fmt()) |
| 110 | 66 | ||
| 111 | lines, labels = zip(*style_map.get_key()) | 67 | axes.set_title(details.title) |
| 112 | 68 | ||
| 113 | axes.legend(tuple(lines), tuple(labels), prop={'size':10}) | 69 | lines, labels = zip(*style_map.get_key()) |
| 114 | axes.set_ylabel(details.value) | 70 | axes.legend(tuple(lines), tuple(labels), prop={'size':10}) |
| 115 | axes.set_xlabel(details.variable) | ||
| 116 | axes.set_xlim(0, axes.get_xlim()[1] + 1) | ||
| 117 | axes.set_ylim(0, axes.get_ylim()[1] + 1) | ||
| 118 | 71 | ||
| 119 | axes.set_title(details.title) | 72 | axes.set_ylabel(details.value) |
| 73 | axes.set_xlabel(details.variable) | ||
| 74 | axes.set_xlim(0, axes.get_xlim()[1] + 1) | ||
| 75 | axes.set_ylim(0, axes.get_ylim()[1] + 1) | ||
| 120 | 76 | ||
| 121 | plot.savefig(out_fname, format=OUT_FORMAT) | 77 | plot.savefig(details.out, format=OUT_FORMAT) |
| 122 | 78 | ||
| 123 | sys.stderr.write('\r {0:.2%}'.format(float(id)/num_plots)) | 79 | def plot_dir(data_dir, out_dir, force): |
| 124 | sys.stderr.write('\n') | 80 | sys.stderr.write("Reading data...\n") |
| 125 | |||
| 126 | def plot_exp(data_dir, out_dir, force): | ||
| 127 | print("Reading data...") | ||
| 128 | dir_map = DirMap.read(data_dir) | 81 | dir_map = DirMap.read(data_dir) |
| 129 | print("Sorting configs...") | 82 | |
| 83 | sys.stderr.write("Creating column map...\n") | ||
| 130 | tuple_table = ReducedTupleTable.from_dir_map(dir_map) | 84 | tuple_table = ReducedTupleTable.from_dir_map(dir_map) |
| 131 | col_map = tuple_table.get_col_map() | 85 | col_map = tuple_table.get_col_map() |
| 132 | 86 | ||
| 133 | if not os.path.exists(out_dir): | 87 | if not os.path.exists(out_dir): |
| 134 | os.mkdir(out_dir) | 88 | os.mkdir(out_dir) |
| 135 | 89 | ||
| 136 | print("Plotting data...") | 90 | sys.stderr.write("Plotting...\n") |
| 137 | plot_by_variable(dir_map, col_map, out_dir, force) | 91 | |
| 138 | # plot_by_config(tuple_table, out_dir) | 92 | # Count total plots for % counter |
| 93 | num_plots = len([x for x in dir_map.leafs(1)]) | ||
| 94 | plot_num = 0 | ||
| 95 | |||
| 96 | for plot_path, plot_node in dir_map.leafs(1): | ||
| 97 | details = get_details(plot_path, out_dir) | ||
| 98 | |||
| 99 | if force or not os.path.exists(details.out): | ||
| 100 | plot_by_variable(plot_node, col_map, details) | ||
| 101 | |||
| 102 | plot_num += 1 | ||
| 103 | |||
| 104 | sys.stderr.write('\r {0:.2%}'.format(float(plot_num)/num_plots)) | ||
| 105 | sys.stderr.write('\n') | ||
| 139 | 106 | ||
| 140 | def main(): | 107 | def main(): |
| 141 | opts, args = parse_args() | 108 | opts, args = parse_args() |
| @@ -146,13 +113,8 @@ def main(): | |||
| 146 | if not os.path.exists(opts.out_dir): | 113 | if not os.path.exists(opts.out_dir): |
| 147 | os.mkdir(opts.out_dir) | 114 | os.mkdir(opts.out_dir) |
| 148 | 115 | ||
| 149 | for exp in args: | 116 | for dir in args: |
| 150 | name = os.path.split(exp)[1] | 117 | plot_dir(dir, opts.out_dir, opts.force) |
| 151 | if exp != os.getcwd(): | ||
| 152 | out_dir = "%s/%s" % (opts.out_dir, name) | ||
| 153 | else: | ||
| 154 | out_dir = os.getcwd() | ||
| 155 | plot_exp(exp, out_dir, opts.force) | ||
| 156 | 118 | ||
| 157 | if __name__ == '__main__': | 119 | if __name__ == '__main__': |
| 158 | main() | 120 | main() |
