|
| 1 | +import plotly.graph_objs as go |
| 2 | + |
| 3 | +from collections import namedtuple |
| 4 | + |
| 5 | + |
| 6 | +default_color_seq = ["#3366cc", "#dc3912", "#ff9900", "#109618", |
| 7 | + "#990099", "#0099c6", "#dd4477", "#66aa00", |
| 8 | + "#b82e2e", "#316395", "#994499", "#22aa99", |
| 9 | + "#aaaa11", "#6633cc", "#e67300", "#8b0707", |
| 10 | + "#651067", "#329262", "#5574a6", "#3b3eac"] |
| 11 | +default_symbol_seq = ["circle", "triangle-down", "square", "x", "cross"] |
| 12 | +default_dash_seq = ["solid", "dot", "dash", |
| 13 | + "longdash", "dashdot", "longdashdot"] |
| 14 | +Mapping = namedtuple('Mapping', ['grouper', 'val_map', 'sequence', 'updater']) |
| 15 | + |
| 16 | + |
| 17 | +def make_mapping(variable, parent, args): |
| 18 | + return Mapping( |
| 19 | + grouper=args[variable], |
| 20 | + val_map=args[variable+"_map"].copy(), |
| 21 | + sequence=args[variable+"_sequence"], |
| 22 | + updater=lambda x: {parent: {variable: x}}, |
| 23 | + ) |
| 24 | + |
| 25 | + |
| 26 | +def make_figure(df, constructor, trace_kwargs_by_group, mappings): |
| 27 | + fig = go.FigureWidget( |
| 28 | + {"layout": {'template': 'plotly', 'hovermode': 'closest'}}) |
| 29 | + |
| 30 | + def one_group(x): return "" |
| 31 | + grouper = [x.grouper or one_group for x in mappings] or [one_group] |
| 32 | + for group_name, group in df.groupby(grouper): |
| 33 | + if len(grouper) == 1: |
| 34 | + group_name = [group_name] |
| 35 | + mapping_str = [] |
| 36 | + for col, val in zip(grouper, group_name): |
| 37 | + if col != one_group: |
| 38 | + s = "%s=%s" % (col, val) |
| 39 | + if s not in mapping_str: |
| 40 | + mapping_str.append(s) |
| 41 | + trace = constructor(name=", ".join(mapping_str)) |
| 42 | + for i, m in enumerate(mappings): |
| 43 | + val = group_name[i] |
| 44 | + if val not in m.val_map: |
| 45 | + m.val_map[val] = m.sequence[len(m.val_map) % len(m.sequence)] |
| 46 | + trace.update(m.updater(m.val_map[val])) |
| 47 | + trace.update(trace_kwargs_by_group(group)) |
| 48 | + fig.add_trace(trace) |
| 49 | + return fig |
| 50 | + |
| 51 | + |
| 52 | +def cartesian_axes(fig, args): |
| 53 | + fig.layout.xaxis.title = args["x"] |
| 54 | + fig.layout.yaxis.title = args["y"] |
| 55 | + if args["log_x"]: |
| 56 | + fig.layout.xaxis.type = "log" |
| 57 | + if args["log_y"]: |
| 58 | + fig.layout.yaxis.type = "log" |
| 59 | + |
| 60 | + |
| 61 | +def scatter(df, x, y, color=None, symbol=None, size=None, name=None, |
| 62 | + color_map={}, symbol_map={}, |
| 63 | + color_sequence=default_color_seq, |
| 64 | + symbol_sequence=default_symbol_seq, |
| 65 | + log_x=False, log_y=False): |
| 66 | + if size: |
| 67 | + sizeref = df[size].max() / (45*45) |
| 68 | + fig = make_figure( |
| 69 | + df, go.Scatter, |
| 70 | + lambda g: dict(mode='markers', x=g[x], y=g[y], |
| 71 | + hovertext=name and g[name], |
| 72 | + marker=size and dict( |
| 73 | + size=g[size], sizemode="area", sizeref=sizeref) |
| 74 | + ), |
| 75 | + [ |
| 76 | + make_mapping("color", "marker", locals()), |
| 77 | + make_mapping("symbol", "marker", locals()) |
| 78 | + ] |
| 79 | + ) |
| 80 | + cartesian_axes(fig, locals()) |
| 81 | + return fig |
| 82 | + |
| 83 | + |
| 84 | +def density_heatmap(df, x, y, log_x=False, log_y=False): |
| 85 | + fig = make_figure( |
| 86 | + df, go.Histogram2d, |
| 87 | + lambda g: dict(x=g[x], y=g[y]), |
| 88 | + [] |
| 89 | + ) |
| 90 | + cartesian_axes(fig, locals()) |
| 91 | + return fig |
| 92 | + |
| 93 | + |
| 94 | +def density_contour(df, x, y, log_x=False, log_y=False): |
| 95 | + fig = make_figure( |
| 96 | + df, go.Histogram2dContour, |
| 97 | + lambda g: dict(x=g[x], y=g[y]), |
| 98 | + [] |
| 99 | + ) |
| 100 | + cartesian_axes(fig, locals()) |
| 101 | + return fig |
| 102 | + |
| 103 | + |
| 104 | +def line(df, x, y, color=None, dash=None, |
| 105 | + color_map={}, dash_map={}, |
| 106 | + color_sequence=default_color_seq, |
| 107 | + dash_sequence=default_dash_seq, log_x=False, log_y=False): |
| 108 | + fig = make_figure( |
| 109 | + df, go.Scatter, |
| 110 | + lambda g: dict(mode='lines', x=g[x], y=g[y]), |
| 111 | + [ |
| 112 | + make_mapping("color", "marker", locals()), |
| 113 | + make_mapping("dash", "line", locals()) |
| 114 | + ] |
| 115 | + ) |
| 116 | + cartesian_axes(fig, locals()) |
| 117 | + return fig |
| 118 | + |
| 119 | + |
| 120 | +def bar(df, x, y, color=None, color_map={}, color_sequence=default_color_seq, |
| 121 | + orientation='v', normalization="", mode="relative", log_x=False, log_y=False): |
| 122 | + fig = make_figure( |
| 123 | + df, go.Bar, |
| 124 | + lambda g: dict(x=g[x], y=g[y], orientation=orientation), |
| 125 | + [make_mapping("color", "marker", locals())] |
| 126 | + ) |
| 127 | + cartesian_axes(fig, locals()) |
| 128 | + fig.layout.barnorm = normalization |
| 129 | + fig.layout.barmode = mode |
| 130 | + return fig |
| 131 | + |
| 132 | + |
| 133 | +def histogram(df, x, y, color=None, color_map={}, color_sequence=default_color_seq, |
| 134 | + orientation='v', mode="stack", normalization=None, log_x=False, log_y=False): |
| 135 | + fig = make_figure( |
| 136 | + df, go.Histogram, |
| 137 | + lambda g: dict(x=g[x], y=g[y], orientation=orientation, |
| 138 | + histnorm=normalization), |
| 139 | + [make_mapping("color", "marker", locals())] |
| 140 | + ) |
| 141 | + cartesian_axes(fig, locals()) |
| 142 | + fig.layout.barmode = mode |
| 143 | + return fig |
| 144 | + |
| 145 | + |
| 146 | +def violin(df, x, y, color=None, color_map={}, color_sequence=default_color_seq, |
| 147 | + orientation='v', mode="group", log_x=False, log_y=False): |
| 148 | + fig = make_figure( |
| 149 | + df, go.Violin, |
| 150 | + lambda g: dict(x=g[x], y=g[y], orientation=orientation), |
| 151 | + [make_mapping("color", "marker", locals())] |
| 152 | + ) |
| 153 | + cartesian_axes(fig, locals()) |
| 154 | + fig.layout.violinmode = mode |
| 155 | + return fig |
| 156 | + |
| 157 | + |
| 158 | +def box(df, x, y, color=None, color_map={}, color_sequence=default_color_seq, |
| 159 | + orientation='v', mode="group", log_x=False, log_y=False): |
| 160 | + fig = make_figure( |
| 161 | + df, go.Box, |
| 162 | + lambda g: dict(x=g[x], y=g[y], orientation=orientation), |
| 163 | + [make_mapping("color", "marker", locals())] |
| 164 | + ) |
| 165 | + cartesian_axes(fig, locals()) |
| 166 | + fig.layout.boxmode = mode |
| 167 | + return fig |
| 168 | + |
| 169 | + |
| 170 | +def scatter_ternary(df, a, b, c, color=None, symbol=None, size=None, |
| 171 | + color_map={}, symbol_map={}, |
| 172 | + color_sequence=default_color_seq, |
| 173 | + symbol_sequence=default_symbol_seq): |
| 174 | + if size: |
| 175 | + sizeref = df[size].max() / (45*45) |
| 176 | + fig = make_figure( |
| 177 | + df, go.Scatterternary, |
| 178 | + lambda g: dict(mode='markers', a=g[a], b=g[b], c=g[c], |
| 179 | + marker=size and dict( |
| 180 | + size=g[size], sizemode="area", sizeref=sizeref) |
| 181 | + ), |
| 182 | + [ |
| 183 | + make_mapping("color", "marker", locals()), |
| 184 | + make_mapping("symbol", "marker", locals()) |
| 185 | + ] |
| 186 | + ) |
| 187 | + fig.layout.ternary.aaxis.title = a |
| 188 | + fig.layout.ternary.baxis.title = b |
| 189 | + fig.layout.ternary.caxis.title = c |
| 190 | + return fig |
| 191 | + |
| 192 | + |
| 193 | +def line_ternary(df, a, b, c, color=None, dash=None, |
| 194 | + color_map={}, dash_map={}, |
| 195 | + color_sequence=default_color_seq, |
| 196 | + dash_sequence=default_dash_seq): |
| 197 | + fig = make_figure( |
| 198 | + df, go.Scatter, |
| 199 | + lambda g: dict(mode='lines', a=g[a], b=g[b], c=g[c]), |
| 200 | + [ |
| 201 | + make_mapping("color", "marker", locals()), |
| 202 | + make_mapping("dash", "line", locals()) |
| 203 | + ] |
| 204 | + ) |
| 205 | + fig.layout.ternary.aaxis.title = a |
| 206 | + fig.layout.ternary.baxis.title = b |
| 207 | + fig.layout.ternary.caxis.title = c |
| 208 | + return fig |
| 209 | + |
| 210 | + |
| 211 | +def scatter_polar(df, r, theta, color=None, symbol=None, size=None, |
| 212 | + color_map={}, symbol_map={}, |
| 213 | + color_sequence=default_color_seq, |
| 214 | + symbol_sequence=default_symbol_seq): |
| 215 | + if size: |
| 216 | + sizeref = df[size].max() / (45*45) |
| 217 | + fig = make_figure( |
| 218 | + df, go.Scatterpolar, |
| 219 | + lambda g: dict(mode='markers', r=g[r], theta=g[theta], |
| 220 | + marker=size and dict( |
| 221 | + size=g[size], sizemode="area", sizeref=sizeref) |
| 222 | + ), |
| 223 | + [ |
| 224 | + make_mapping("color", "marker", locals()), |
| 225 | + make_mapping("symbol", "marker", locals()) |
| 226 | + ] |
| 227 | + ) |
| 228 | + return fig |
| 229 | + |
| 230 | + |
| 231 | +def line_polar(df, r, theta, color=None, dash=None, |
| 232 | + color_map={}, dash_map={}, |
| 233 | + color_sequence=default_color_seq, |
| 234 | + dash_sequence=default_dash_seq): |
| 235 | + fig = make_figure( |
| 236 | + df, go.Scatter, |
| 237 | + lambda g: dict(mode='lines', r=g[r], theta=g[theta]), |
| 238 | + [ |
| 239 | + make_mapping("color", "marker", locals()), |
| 240 | + make_mapping("dash", "line", locals()) |
| 241 | + ] |
| 242 | + ) |
| 243 | + return fig |
| 244 | + |
| 245 | + |
| 246 | +def bar_polar(df, r, theta, color=None, color_map={}, color_sequence=default_color_seq, |
| 247 | + normalization="", mode="relative"): |
| 248 | + fig = make_figure( |
| 249 | + df, go.Barpolar, |
| 250 | + lambda g: dict(r=g[r], theta=g[theta]), |
| 251 | + [ |
| 252 | + make_mapping("color", "marker", locals()) |
| 253 | + ] |
| 254 | + ) |
| 255 | + fig.layout.barnorm = normalization |
| 256 | + fig.layout.barmode = mode |
| 257 | + return fig |
| 258 | + |
| 259 | + |
| 260 | +def splom(df, dimensions=None, color=None, symbol=None, |
| 261 | + color_map={}, symbol_map={}, |
| 262 | + color_sequence=default_color_seq, |
| 263 | + symbol_sequence=default_symbol_seq): |
| 264 | + fig = make_figure( |
| 265 | + df, go.Splom, |
| 266 | + lambda g: dict(dimensions=[ |
| 267 | + dict(label=name, values=column.values) |
| 268 | + for name, column in g.iteritems() |
| 269 | + if ( |
| 270 | + (dimensions and name in dimensions) or |
| 271 | + (not dimensions and column.dtype in ["int64", "float64"]) |
| 272 | + ) |
| 273 | + ]), |
| 274 | + [ |
| 275 | + make_mapping("color", "marker", locals()), |
| 276 | + make_mapping("symbol", "marker", locals()) |
| 277 | + ] |
| 278 | + ) |
| 279 | + return fig |
| 280 | + |
| 281 | +# TODO canonical examples ... needed now! |
| 282 | +# TODO test with none, all, any mappings |
| 283 | +# TODO test each plot |
| 284 | +# TODO test defaults |
| 285 | +# TODO histogram weights and calcs |
| 286 | +# TODO various box and violin options |
| 287 | +# TODO log scales in SPLOM and generally in facets |
| 288 | +# TODO check on dates |
| 289 | +# TODO facets |
| 290 | +# TODO marginals |
| 291 | +# TODO validate inputs |
| 292 | +# TODO name / hover labels |
| 293 | +# TODO opacity |
| 294 | +# TODO continuous color |
| 295 | +# TODO color splits in densities |
| 296 | +# TODO groupby ignores NaN ... ? |
| 297 | +# TODO suppress plotly.py errors... don't show our programming errors? |
| 298 | +# TODO parcoords, parcats |
0 commit comments