Save data directly in Mathematica notebooks

Mathematica 11.3—released in March 2018—has introduced the Iconize feature, which largely replaces the techniques described below.

Mathematica notebooks are a convenient document format for storing code, text, graphics, and human readable computation results. But sometimes it would be useful if we could also use them to store machine-readable datasets.

For example, sometimes I run a calculation which takes several minutes to complete. I would like to save its result, so that next time I use the notebook I do not need to wait for the data to be re-generated. Usually I would export the result to a file, so I can re-load it later. But I may not yet have a project directory set up, to keep the data in. It would be much more convenient to store everything in a single file: directly within the notebook.

How do we do this?

When the data is small, we can create a cell that assigns it to variable, like this:

var = {1, 2, ...};

Sometimes I do use this method. But more often the data is just too large to display it this way. It may take up several pages.

One way to deal with this is to create an object which has a compact display in the notebook, but in computations it is interpreted as the full dataset. We can do this manually using the Interpretation function. Simply evaluate the following:

Interpretation["<<data>>", Evaluate[var]]

Then edit the output cell, and write var = in front of <<data>>, like this:

Use Interpretation to shorten the display of data

This is still a bit too much work to use regularly, so I wrapped it up into one convenient function, with some additional features.

ClearAll[SaveToCell]

SaveToCell::usage =
    "SaveToCell[variable] creates an input cell that reassigns the current value of variable.\n" <>
    "SaveToCell[variables, display] shows 'display' on the right-hand-side of the assignment.";

SetAttributes[SaveToCell, HoldFirst]
SaveToCell[var_, name : Except[_?OptionQ] : "data", opt : OptionsPattern[]] :=
    With[{data = Compress[var],
      panel = ToBoxes@Tooltip[Panel[name, FrameMargins -> Small], DateString[]]},
      CellPrint@Cell[
        BoxData@RowBox[{
          MakeBoxes[var],
          "=",
          InterpretationBox[panel, Uncompress[data]],
          ";"
        }],
        "Input",
        GeneratedCell -> False, (* prevent deletion by Cell > Delete All Output: *)
        CellLabel -> "(saved)",
        opt,
        CellLabelAutoDelete -> False
      ]
    ]

I have been using this function for a few weeks now, and I find it quite useful. I believe it should work all the way back to Mathematica version 6.0, although I have only tested it with versions 9.0 and later.

Here’s how to use it:

var = Range[1000];
SaveToCell[var]

This automatically creates an annotated input cell with a compact display that, when evaluated, will re-assign the data to the variable var:

var = [data];

Instead of displaying the full dataset, it just shows a small panel with a label. We can easily customize the label using the second argument of SaveToCell:

SaveToCell[var, Short[var]]

var = [1, 2, ..., 999, 1000];

Hovering it with the mouse shows a tooltip with the date when the cell was created:

Short data display with dated tooltip.

SaveToCell will also take any standard Cell option. We could change the cell label like this:

SaveToCell[var, CellLabel -> "My Data"]

var = [data];

Or we can protect the cell against accidental deletion:

SaveToCell[var, Deletable -> False]

To remove this protection, select the cell, open the Options Inspector through the Format menu, and under Cell Options → General Properties mark it as Deletable again.

SaveToCell also Compresses the data before saving it, so that it will not take up too much space in the notebook. Do keep in mind though that notebooks are not designed to hold a large amount of data. I do not recommend saving data that is larger than a few megabytes in size after compression.

If your data is large (several tens of megabytes or larger), consider saving it into a separate file. There are several ready-to-use file formats that can hold abirtrary Mathematica expressions. The most prominent ones are plain text expressions (implemented as the "Package" format in the Import/Export framework), "WDX" and "MX". These all have some disadvantages though: plain text expressions are large, WDX is slow to read/write, and MX has limited cross version and cross platform compatibility.

Thus I recommend a different approach: Compress the data and simply export it as a string. I use the following functions for this:

ZImport::usage = "ZImport[\"file.mz\"] will import a compressed Mathematica expression from file.mz."
ZExport::usage = "ZExport[\"file.mz\", data] will export data as a compressed Mathematica expression to file.mz."

ZImport[filename_] := Uncompress@Import[filename, "String"]
ZExport[filename_, data_] := Export[filename, Compress[data], "String"]

This method is a good compromise between speed, file size and compatibility. Files saved like this are typically both backward and forward compatible between versions. The size is much smaller than a plain text representation and the performance is much better than that of WDX, but not as good as MX.

Comments !