1

I'm writing python classes for use in Jupyter Notebook that should have pretty representations. For this, I define _repr_html_ methods (that can embed graphics generated on-the-fly by matplotlib). But sometimes I want to include math as well. For example,

import pandas as pd
import IPython.display as idisp

class FooHtml:
    def __init__(self):
        self.df = pd.DataFrame({'alpha': [1.0, 2, 3], 'beta': [0.1, 0.2, 0.3]})

    def _repr_html_(self):
        math = idisp.Math(r'\alpha = \int f(\tau)\,d\tau')
        return (
            '<h3>FooHtml</h3>'  
            f'{self.df._repr_html_()}<br>\n'
            f'Explanation: {math}, where tau is the time.'
        )

FooHtml()

This cell input will generate the following output:

Jupyter display

This doesn't show the math, although display(math) does show an equation:

math formula

The non-rendering is because str(math) == '<IPython.core.display ...>' will be interpreted as an invalid HTML tag. The question is: how do I actually render math embedded in a HTML representation. I figured out the answer, which I couldn't find anywhere; which I'll post as an answer.

Han-Kwang Nienhuys
  • 3,084
  • 2
  • 12
  • 31

2 Answers2

2

The HTML string returned by _repr_html_ may contain LaTeX-formatted math within $$ ... $$ and $ ... $ strings. Although other Markdown formatting is not parsed from the output of _repr_html_, $-delimited math is actually parsed:

class FooHtml:
    def __init__(self):
        self.df = pd.DataFrame({'alpha': [1.0, 2, 3], 'beta': [0.1, 0.2, 0.3]})

    def _repr_html_(self):
        math = idisp.Math(r'') 
        return (
            '<h3>FooHtml</h3>'
            f'{self.df._repr_html_()}<br>\n'
            r'Explanation: $$\alpha = \int f(\tau)\,d\tau,$$'
            r'where $\tau$ is the time.'
        )

FooHtml()

Output:

Jupyter display

Alternatively, you can create a _repr_markdown_ method. Although DataFrame._repr_markdown_ does not exist, it is fine to embed an HTML table within Markdown:

class FooMarkdown:
    def __init__(self):
        self.df = pd.DataFrame({'alpha': [1.0, 2, 3], 'beta': [0.1, 0.2, 0.3]})

    def _repr_markdown_(self):
        return (
            '### FooMarkDown'
            f'{self.df._repr_html_()}<br>\n'
            r'Explanation: $$\alpha = \int f(\tau)\,d\tau$$ where $\tau$ is the time'
        )


FooMarkdown()

Jupyter display

Han-Kwang Nienhuys
  • 3,084
  • 2
  • 12
  • 31
0

This is exactly the sort of issue that motivated me to create jupydoc, also using markdown to unify graphics, formatted text, and latex.

With its machinery an answer is easy:

from jupydoc import Publisher

class FooMarkdown(Publisher):
    def foo(self):
        r"""
        ### FooHTML
        
        {df}
        
        Explanation:
        
        $\alpha = \int f(\tau)\,d\tau$
        
        where $\tau$ is the time.
        """
        df = pd.DataFrame({'alpha': [1.0, 2, 3], 'beta': [0.1, 0.2, 0.3]})
        self.publishme()
FooMarkdown().foo()

Resulting in:

enter image description here

vvvvv
  • 25,404
  • 19
  • 49
  • 81
  • Interesting approach. But from the example I don't see how I can integrate this with the class `__repr__` and `_repr_html_` methods. If `foo` is an instance of `FooMarkdown`, I like `In[123]: foo` to result in a pretty representation in `Out[123]`. – Han-Kwang Nienhuys Jul 04 '20 at 13:40
  • (Thanks for fixing my post: this is my first ever.) This is indeed ditterent, in that the JupyterLab notebook displays the "pretty representation" without it appearing in Out. The HTML output to a file looks just the same, so I don't understand that need. To have it appear there, however, would only involve the function returning the markdown output, which is available as an attribute `data` of the FooMarkdown class. – Toby Burnett Jul 05 '20 at 16:16
  • When I work in Jupyter Notebook, it's typically for explorative data analysis. I will deal with objects for which I don't remember the datatype. If I want to inspect the value of `foo`, I type `foo` in a cell and the notebook kernel will figure out the best way to display it. I'd rather not type `foo.prettyprint()`, `bar.print()`, `baz.show()`, because it's extra clutter and because I have to remember the member names. – Han-Kwang Nienhuys Jul 05 '20 at 16:35
  • So I added a method `_repr_mimebundle_` that satisfies such a need, and will be useful for me. But I too use this in an explorative mode, but in a context of incorporating it into a document that will summarize the results of the exploration. It is easy to change the behavior to not generate the display immediately, but when the object is returned, triggering the _repr_... stuff. And, much easier to simply *inherit* the behavior I want. – Toby Burnett Jul 05 '20 at 17:37