Printing#

Aside from plotting result, printing numbers is probably the main way you do science. (Or maybe your science consists entirely of listening to birdsong.) This tutorial can’t help make the numbers in your science better, but it can help you figure out more quickly if they’re good numbers or not.

Click here to open an interactive version of this notebook.

Headings and colors#

No one in their right mind would make a black and white plot these days, but it’s still pretty common to output monochrome text. Fair: color should be used sparingly. But when you do want a pop of color, Sciris has you covered. For example, you can easily make section headings to delineate large blocks of text:

[1]:
import sciris as sc
import numpy as np

sc.heading('A very long green story')
string = '"Once upon a time, there was a story that began: '
sc.printgreen(sc.indent(string*20 + ' ...'))

sc.heading('Some very dull blue data')
sc.printblue(np.random.rand(10,6))


———————————————————————
A very long green story
———————————————————————

"Once upon a time, there was a story that began: "Once upon a time,
there was a story that began: "Once upon a time, there was a story
that began: "Once upon a time, there was a story that began: "Once
upon a time, there was a story that began: "Once upon a time, there
was a story that began: "Once upon a time, there was a story that
began: "Once upon a time, there was a story that began: "Once upon a
time, there was a story that began: "Once upon a time, there was a
story that began: "Once upon a time, there was a story that began:
"Once upon a time, there was a story that began: "Once upon a time,
there was a story that began: "Once upon a time, there was a story
that began: "Once upon a time, there was a story that began: "Once
upon a time, there was a story that began: "Once upon a time, there
was a story that began: "Once upon a time, there was a story that
began: "Once upon a time, there was a story that began: "Once upon a
time, there was a story that began:  ...



————————————————————————
Some very dull blue data
————————————————————————

[[0.2509373  0.29069357 0.04694163 0.13138685 0.27393625 0.66307483]
 [0.81803639 0.4834537  0.35670898 0.32928641 0.19469609 0.84122186]
 [0.82887075 0.715332   0.96849718 0.18468315 0.45170719 0.70519435]
 [0.60187651 0.15904728 0.14428153 0.4029997  0.4176373  0.72771795]
 [0.4652077  0.03978393 0.81337311 0.54713226 0.35399059 0.89993072]
 [0.09369407 0.08433511 0.51324353 0.61481404 0.90271602 0.3873683 ]
 [0.91649208 0.93085804 0.80680749 0.10236831 0.84102692 0.13804236]
 [0.52108519 0.85610819 0.08672989 0.45863748 0.16347145 0.97796116]
 [0.78052219 0.36203528 0.69362508 0.66160531 0.58734594 0.18883726]
 [0.14334032 0.27120693 0.17903288 0.54879042 0.61854327 0.55767797]]

(Note: if you’re reading this on docs.sciris.org, there’s a little button at the top right where you can change to dark mode if you prefer – the colors might make more sense then!)

Incidentally, Sciris includes two functions for combining strings: sc.strjoin() and sc.newlinejoin(). These are just shortcuts to ', '.join() and '\n'.join(), respectively (plus automatic conversion to strings), but can make life easier, especially inside f-strings:

[2]:
def get(key):
    my_dict = dict(key1=1, key2=2, key3=3)
    try:
        my_dict[key]
    except:
        errormsg = f'Invalid key {key}; must be {sc.strjoin(my_dict.keys())}, which have values:\n{sc.newlinejoin(my_dict.items())}'
        print(errormsg)

get('key4')
Invalid key key4; must be key1, key2, key3, which have values:
('key1', 1)
('key2', 2)
('key3', 3)

Printing objects#

Let’s revisit our well-trodden sim:

[3]:
import numpy as np
import pylab as pl

class Sim:
    def __init__(self, n=10, n_factors=5):
        self.n = n
        self.n_factors = n_factors
        self.results = sc.objdict()
        self.ready = False

    def run(self):
        for i in range(self.n_factors):
            label = f'i={i+1}'
            result = np.random.randint(0, 10, self.n)**(i+1)
            self.results[label] = result
        self.ready = True

    def plot(self):
        pl.plot(self.results[:])

sim = Sim()
sim.run()

We can quickly view the full object with the “pretty representation”, or sc.pr():

[4]:
sc.pr(sim)
<__main__.Sim at 0x7f602a8f8250>
[<class '__main__.Sim'>]
————————————————————————————————————————————————————————————————————————
Methods:
  plot()                  run()
————————————————————————————————————————————————————————————————————————
        n: 10
n_factors: 5
    ready: True
  results: #0. 'i=1': array([4, 0, 2, 5, 4, 6, 0, 7, 2, 2])
           #1. 'i=2': array([64,  [...]
————————————————————————————————————————————————————————————————————————

Compare this to the standard but less informative dir():

[5]:
dir(sim)
[5]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getstate__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'n',
 'n_factors',
 'plot',
 'ready',
 'results',
 'run']

Trying to figure out what this means is a lot more work! For example, from dir(), you would’t know if run is an attribute (is it a flag indicating that the sim was run?) or a method.

In fact, this representation of an object is so useful, you can use it when you create the class. Then if you do print(sim), you’ll get the full representation rather than just the default (class name and memory address):

[6]:
class PrettySim(sc.prettyobj): # This line is key, everything else is the same as before!
    def __init__(self, n=10, n_factors=5):
        self.n = n
        self.n_factors = n_factors
        self.results = sc.objdict()
        self.ready = False

    def run(self):
        for i in range(self.n_factors):
            label = f'i={i+1}'
            result = np.random.randint(0, 10, self.n)**(i+1)
            self.results[label] = result
        self.ready = True

    def plot(self):
        pl.plot(self.results[:])

sim = PrettySim()
sim.run()
print(sim)
<__main__.PrettySim at 0x7f602a5931d0>
[<class '__main__.PrettySim'>, <class 'sciris.sc_printing.prettyobj'>]
————————————————————————————————————————————————————————————————————————
Methods:
  plot()                  run()
————————————————————————————————————————————————————————————————————————
        n: 10
n_factors: 5
    ready: True
  results: #0. 'i=1': array([6, 8, 2, 4, 1, 3, 5, 8, 2, 9])
           #1. 'i=2': array([25,  [...]
————————————————————————————————————————————————————————————————————————

(Some readers may question whether this representation is more useful than it is pretty. Point taken.)

Monitoring progress#

What if you have a really slow task and you want to check progress? You can use sc.progressbar for that, which builds on the excellent package tqdm:

[7]:
class SlowSim(PrettySim):

    def run_slow(self):
        for i in sc.progressbar(range(self.n_factors)): # This is the only change!
            sc.randsleep(0.2) # Make it slow
            label = f'i={i+1}'
            result = np.random.randint(0, 10, self.n)**(i+1)
            self.results[label] = result
        self.ready = True

slowsim = SlowSim()
slowsim.run_slow()
100%|██████████| 5/5 [00:01<00:00,  3.77it/s]

Note that the progress bar looks better in a regular terminal than in Jupyter, and needless to say, it doesn’t look like anything in a static web page!