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() |