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 /plot_exps.py | |
| parent | b2fa65ecfe14bb9377fbd8afa5f457a07472b6fb (diff) | |
Optimized plot script to handle different directory structures.
Diffstat (limited to 'plot_exps.py')
| -rwxr-xr-x | plot_exps.py | 154 |
1 files changed, 58 insertions, 96 deletions
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() |
