📚 The CoCalc Library - books, templates and other resources
License: OTHER
"""This file contains code for use with "Think Stats",1by Allen B. Downey, available from greenteapress.com23Copyright 2014 Allen B. Downey4License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html5"""67from __future__ import print_function89import math10import matplotlib11import matplotlib.pyplot as pyplot12import numpy as np13import pandas1415import warnings1617# customize some matplotlib attributes18#matplotlib.rc('figure', figsize=(4, 3))1920#matplotlib.rc('font', size=14.0)21#matplotlib.rc('axes', labelsize=22.0, titlesize=22.0)22#matplotlib.rc('legend', fontsize=20.0)2324#matplotlib.rc('xtick.major', size=6.0)25#matplotlib.rc('xtick.minor', size=3.0)2627#matplotlib.rc('ytick.major', size=6.0)28#matplotlib.rc('ytick.minor', size=3.0)293031class _Brewer(object):32"""Encapsulates a nice sequence of colors.3334Shades of blue that look good in color and can be distinguished35in grayscale (up to a point).3637Borrowed from http://colorbrewer2.org/38"""39color_iter = None4041colors = ['#081D58',42'#253494',43'#225EA8',44'#1D91C0',45'#41B6C4',46'#7FCDBB',47'#C7E9B4',48'#EDF8B1',49'#FFFFD9']5051# lists that indicate which colors to use depending on how many are used52which_colors = [[],53[1],54[1, 3],55[0, 2, 4],56[0, 2, 4, 6],57[0, 2, 3, 5, 6],58[0, 2, 3, 4, 5, 6],59[0, 1, 2, 3, 4, 5, 6],60]6162@classmethod63def Colors(cls):64"""Returns the list of colors.65"""66return cls.colors6768@classmethod69def ColorGenerator(cls, n):70"""Returns an iterator of color strings.7172n: how many colors will be used73"""74for i in cls.which_colors[n]:75yield cls.colors[i]76raise StopIteration('Ran out of colors in _Brewer.ColorGenerator')7778@classmethod79def InitializeIter(cls, num):80"""Initializes the color iterator with the given number of colors."""81cls.color_iter = cls.ColorGenerator(num)8283@classmethod84def ClearIter(cls):85"""Sets the color iterator to None."""86cls.color_iter = None8788@classmethod89def GetIter(cls):90"""Gets the color iterator."""91if cls.color_iter is None:92cls.InitializeIter(7)9394return cls.color_iter959697def PrePlot(num=None, rows=None, cols=None):98"""Takes hints about what's coming.99100num: number of lines that will be plotted101rows: number of rows of subplots102cols: number of columns of subplots103"""104if num:105_Brewer.InitializeIter(num)106107if rows is None and cols is None:108return109110if rows is not None and cols is None:111cols = 1112113if cols is not None and rows is None:114rows = 1115116# resize the image, depending on the number of rows and cols117size_map = {(1, 1): (8, 6),118(1, 2): (14, 6),119(1, 3): (14, 6),120(2, 2): (10, 10),121(2, 3): (16, 10),122(3, 1): (8, 10),123}124125if (rows, cols) in size_map:126fig = pyplot.gcf()127fig.set_size_inches(*size_map[rows, cols])128129# create the first subplot130if rows > 1 or cols > 1:131pyplot.subplot(rows, cols, 1)132global SUBPLOT_ROWS, SUBPLOT_COLS133SUBPLOT_ROWS = rows134SUBPLOT_COLS = cols135136137def SubPlot(plot_number, rows=None, cols=None):138"""Configures the number of subplots and changes the current plot.139140rows: int141cols: int142plot_number: int143"""144rows = rows or SUBPLOT_ROWS145cols = cols or SUBPLOT_COLS146pyplot.subplot(rows, cols, plot_number)147148149def _Underride(d, **options):150"""Add key-value pairs to d only if key is not in d.151152If d is None, create a new dictionary.153154d: dictionary155options: keyword args to add to d156"""157if d is None:158d = {}159160for key, val in options.items():161d.setdefault(key, val)162163return d164165166def Clf():167"""Clears the figure and any hints that have been set."""168global LOC169LOC = None170_Brewer.ClearIter()171pyplot.clf()172fig = pyplot.gcf()173fig.set_size_inches(8, 6)174175176def Figure(**options):177"""Sets options for the current figure."""178_Underride(options, figsize=(6, 8))179pyplot.figure(**options)180181182def _UnderrideColor(options):183if 'color' in options:184return options185186color_iter = _Brewer.GetIter()187188if color_iter:189try:190options['color'] = next(color_iter)191except StopIteration:192# TODO: reconsider whether this should warn193# warnings.warn('Warning: Brewer ran out of colors.')194_Brewer.ClearIter()195return options196197198def Plot(obj, ys=None, style='', **options):199"""Plots a line.200201Args:202obj: sequence of x values, or Series, or anything with Render()203ys: sequence of y values204style: style string passed along to pyplot.plot205options: keyword args passed to pyplot.plot206"""207options = _UnderrideColor(options)208label = getattr(obj, 'label', '_nolegend_')209options = _Underride(options, linewidth=3, alpha=0.8, label=label)210211xs = obj212if ys is None:213if hasattr(obj, 'Render'):214xs, ys = obj.Render()215if isinstance(obj, pandas.Series):216ys = obj.values217xs = obj.index218219if ys is None:220pyplot.plot(xs, style, **options)221else:222pyplot.plot(xs, ys, style, **options)223224225def FillBetween(xs, y1, y2=None, where=None, **options):226"""Plots a line.227228Args:229xs: sequence of x values230y1: sequence of y values231y2: sequence of y values232where: sequence of boolean233options: keyword args passed to pyplot.fill_between234"""235options = _UnderrideColor(options)236options = _Underride(options, linewidth=0, alpha=0.5)237pyplot.fill_between(xs, y1, y2, where, **options)238239240def Bar(xs, ys, **options):241"""Plots a line.242243Args:244xs: sequence of x values245ys: sequence of y values246options: keyword args passed to pyplot.bar247"""248options = _UnderrideColor(options)249options = _Underride(options, linewidth=0, alpha=0.6)250pyplot.bar(xs, ys, **options)251252253def Scatter(xs, ys=None, **options):254"""Makes a scatter plot.255256xs: x values257ys: y values258options: options passed to pyplot.scatter259"""260options = _Underride(options, color='blue', alpha=0.2,261s=30, edgecolors='none')262263if ys is None and isinstance(xs, pandas.Series):264ys = xs.values265xs = xs.index266267pyplot.scatter(xs, ys, **options)268269270def HexBin(xs, ys, **options):271"""Makes a scatter plot.272273xs: x values274ys: y values275options: options passed to pyplot.scatter276"""277options = _Underride(options, cmap=matplotlib.cm.Blues)278pyplot.hexbin(xs, ys, **options)279280281def Pdf(pdf, **options):282"""Plots a Pdf, Pmf, or Hist as a line.283284Args:285pdf: Pdf, Pmf, or Hist object286options: keyword args passed to pyplot.plot287"""288low, high = options.pop('low', None), options.pop('high', None)289n = options.pop('n', 101)290xs, ps = pdf.Render(low=low, high=high, n=n)291options = _Underride(options, label=pdf.label)292Plot(xs, ps, **options)293294295def Pdfs(pdfs, **options):296"""Plots a sequence of PDFs.297298Options are passed along for all PDFs. If you want different299options for each pdf, make multiple calls to Pdf.300301Args:302pdfs: sequence of PDF objects303options: keyword args passed to pyplot.plot304"""305for pdf in pdfs:306Pdf(pdf, **options)307308309def Hist(hist, **options):310"""Plots a Pmf or Hist with a bar plot.311312The default width of the bars is based on the minimum difference313between values in the Hist. If that's too small, you can override314it by providing a width keyword argument, in the same units315as the values.316317Args:318hist: Hist or Pmf object319options: keyword args passed to pyplot.bar320"""321# find the minimum distance between adjacent values322xs, ys = hist.Render()323324if 'width' not in options:325try:326options['width'] = 0.9 * np.diff(xs).min()327except TypeError:328warnings.warn("Hist: Can't compute bar width automatically."329"Check for non-numeric types in Hist."330"Or try providing width option."331)332333options = _Underride(options, label=hist.label)334options = _Underride(options, align='center')335if options['align'] == 'left':336options['align'] = 'edge'337elif options['align'] == 'right':338options['align'] = 'edge'339options['width'] *= -1340341Bar(xs, ys, **options)342343344def Hists(hists, **options):345"""Plots two histograms as interleaved bar plots.346347Options are passed along for all PMFs. If you want different348options for each pmf, make multiple calls to Pmf.349350Args:351hists: list of two Hist or Pmf objects352options: keyword args passed to pyplot.plot353"""354for hist in hists:355Hist(hist, **options)356357358def Pmf(pmf, **options):359"""Plots a Pmf or Hist as a line.360361Args:362pmf: Hist or Pmf object363options: keyword args passed to pyplot.plot364"""365xs, ys = pmf.Render()366low, high = min(xs), max(xs)367368width = options.pop('width', None)369if width is None:370try:371width = np.diff(xs).min()372except TypeError:373warnings.warn("Pmf: Can't compute bar width automatically."374"Check for non-numeric types in Pmf."375"Or try providing width option.")376points = []377378lastx = np.nan379lasty = 0380for x, y in zip(xs, ys):381if (x - lastx) > 1e-5:382points.append((lastx, 0))383points.append((x, 0))384385points.append((x, lasty))386points.append((x, y))387points.append((x+width, y))388389lastx = x + width390lasty = y391points.append((lastx, 0))392pxs, pys = zip(*points)393394align = options.pop('align', 'center')395if align == 'center':396pxs = np.array(pxs) - width/2.0397if align == 'right':398pxs = np.array(pxs) - width399400options = _Underride(options, label=pmf.label)401Plot(pxs, pys, **options)402403404def Pmfs(pmfs, **options):405"""Plots a sequence of PMFs.406407Options are passed along for all PMFs. If you want different408options for each pmf, make multiple calls to Pmf.409410Args:411pmfs: sequence of PMF objects412options: keyword args passed to pyplot.plot413"""414for pmf in pmfs:415Pmf(pmf, **options)416417418def Diff(t):419"""Compute the differences between adjacent elements in a sequence.420421Args:422t: sequence of number423424Returns:425sequence of differences (length one less than t)426"""427diffs = [t[i+1] - t[i] for i in range(len(t)-1)]428return diffs429430431def Cdf(cdf, complement=False, transform=None, **options):432"""Plots a CDF as a line.433434Args:435cdf: Cdf object436complement: boolean, whether to plot the complementary CDF437transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel'438options: keyword args passed to pyplot.plot439440Returns:441dictionary with the scale options that should be passed to442Config, Show or Save.443"""444xs, ps = cdf.Render()445xs = np.asarray(xs)446ps = np.asarray(ps)447448scale = dict(xscale='linear', yscale='linear')449450for s in ['xscale', 'yscale']:451if s in options:452scale[s] = options.pop(s)453454if transform == 'exponential':455complement = True456scale['yscale'] = 'log'457458if transform == 'pareto':459complement = True460scale['yscale'] = 'log'461scale['xscale'] = 'log'462463if complement:464ps = [1.0-p for p in ps]465466if transform == 'weibull':467xs = np.delete(xs, -1)468ps = np.delete(ps, -1)469ps = [-math.log(1.0-p) for p in ps]470scale['xscale'] = 'log'471scale['yscale'] = 'log'472473if transform == 'gumbel':474xs = xp.delete(xs, 0)475ps = np.delete(ps, 0)476ps = [-math.log(p) for p in ps]477scale['yscale'] = 'log'478479options = _Underride(options, label=cdf.label)480Plot(xs, ps, **options)481return scale482483484def Cdfs(cdfs, complement=False, transform=None, **options):485"""Plots a sequence of CDFs.486487cdfs: sequence of CDF objects488complement: boolean, whether to plot the complementary CDF489transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel'490options: keyword args passed to pyplot.plot491"""492for cdf in cdfs:493Cdf(cdf, complement, transform, **options)494495496def Contour(obj, pcolor=False, contour=True, imshow=False, **options):497"""Makes a contour plot.498499d: map from (x, y) to z, or object that provides GetDict500pcolor: boolean, whether to make a pseudocolor plot501contour: boolean, whether to make a contour plot502imshow: boolean, whether to use pyplot.imshow503options: keyword args passed to pyplot.pcolor and/or pyplot.contour504"""505try:506d = obj.GetDict()507except AttributeError:508d = obj509510_Underride(options, linewidth=3, cmap=matplotlib.cm.Blues)511512xs, ys = zip(*d.keys())513xs = sorted(set(xs))514ys = sorted(set(ys))515516X, Y = np.meshgrid(xs, ys)517func = lambda x, y: d.get((x, y), 0)518func = np.vectorize(func)519Z = func(X, Y)520521x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False)522axes = pyplot.gca()523axes.xaxis.set_major_formatter(x_formatter)524525if pcolor:526pyplot.pcolormesh(X, Y, Z, **options)527if contour:528cs = pyplot.contour(X, Y, Z, **options)529pyplot.clabel(cs, inline=1, fontsize=10)530if imshow:531extent = xs[0], xs[-1], ys[0], ys[-1]532pyplot.imshow(Z, extent=extent, **options)533534535def Pcolor(xs, ys, zs, pcolor=True, contour=False, **options):536"""Makes a pseudocolor plot.537538xs:539ys:540zs:541pcolor: boolean, whether to make a pseudocolor plot542contour: boolean, whether to make a contour plot543options: keyword args passed to pyplot.pcolor and/or pyplot.contour544"""545_Underride(options, linewidth=3, cmap=matplotlib.cm.Blues)546547X, Y = np.meshgrid(xs, ys)548Z = zs549550x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False)551axes = pyplot.gca()552axes.xaxis.set_major_formatter(x_formatter)553554if pcolor:555pyplot.pcolormesh(X, Y, Z, **options)556557if contour:558cs = pyplot.contour(X, Y, Z, **options)559pyplot.clabel(cs, inline=1, fontsize=10)560561562def Text(x, y, s, **options):563"""Puts text in a figure.564565x: number566y: number567s: string568options: keyword args passed to pyplot.text569"""570options = _Underride(options,571fontsize=16,572verticalalignment='top',573horizontalalignment='left')574pyplot.text(x, y, s, **options)575576577LEGEND = True578LOC = None579580def Config(**options):581"""Configures the plot.582583Pulls options out of the option dictionary and passes them to584the corresponding pyplot functions.585"""586names = ['title', 'xlabel', 'ylabel', 'xscale', 'yscale',587'xticks', 'yticks', 'axis', 'xlim', 'ylim']588589for name in names:590if name in options:591getattr(pyplot, name)(options[name])592593# looks like this is not necessary: matplotlib understands text loc specs594loc_dict = {'upper right': 1,595'upper left': 2,596'lower left': 3,597'lower right': 4,598'right': 5,599'center left': 6,600'center right': 7,601'lower center': 8,602'upper center': 9,603'center': 10,604}605606global LEGEND607LEGEND = options.get('legend', LEGEND)608609if LEGEND:610global LOC611LOC = options.get('loc', LOC)612pyplot.legend(loc=LOC)613614615def Show(**options):616"""Shows the plot.617618For options, see Config.619620options: keyword args used to invoke various pyplot functions621"""622clf = options.pop('clf', True)623Config(**options)624pyplot.show()625if clf:626Clf()627628629def Plotly(**options):630"""Shows the plot.631632For options, see Config.633634options: keyword args used to invoke various pyplot functions635"""636clf = options.pop('clf', True)637Config(**options)638import plotly.plotly as plotly639url = plotly.plot_mpl(pyplot.gcf())640if clf:641Clf()642return url643644645def Save(root=None, formats=None, **options):646"""Saves the plot in the given formats and clears the figure.647648For options, see Config.649650Args:651root: string filename root652formats: list of string formats653options: keyword args used to invoke various pyplot functions654"""655clf = options.pop('clf', True)656Config(**options)657658if formats is None:659formats = ['pdf', 'eps']660661try:662formats.remove('plotly')663Plotly(clf=False)664except ValueError:665pass666667if root:668for fmt in formats:669SaveFormat(root, fmt)670if clf:671Clf()672673674def SaveFormat(root, fmt='eps'):675"""Writes the current figure to a file in the given format.676677Args:678root: string filename root679fmt: string format680"""681filename = '%s.%s' % (root, fmt)682print('Writing', filename)683pyplot.savefig(filename, format=fmt, dpi=300)684685686# provide aliases for calling functons with lower-case names687preplot = PrePlot688subplot = SubPlot689clf = Clf690figure = Figure691plot = Plot692text = Text693scatter = Scatter694pmf = Pmf695pmfs = Pmfs696hist = Hist697hists = Hists698diff = Diff699cdf = Cdf700cdfs = Cdfs701contour = Contour702pcolor = Pcolor703config = Config704show = Show705save = Save706707708def main():709color_iter = _Brewer.ColorGenerator(7)710for color in color_iter:711print(color)712713714if __name__ == '__main__':715main()716717718