7
$\begingroup$

The common problem in my work is a need to import some experimental data to work with. This means that my notebook depends on some external files which makes it difficult to share it with others and inconvenient when I return to it after time.

What I wish is to have a way to store the data inside the Notebook in collapsible CellGroups in order to have an ability to view the data in human-readable tabular form and to re-import the data file manually if I change it.

Generally, the data inside of the notebook should look as on the screenshot:

screenshot

The output Сells are regenerated by pressing Shift + Enter when the data files change. What I need is a way to retrieve the data sets from the output cells in the section "The data cells". Something like this:

dataSet1 = getData["Data set 1"];
dataSet2 = getData["Data set 2"];

where "Data set 1" and "Data set 2" are the text strings in the Subsubsection cells from the section "The data cells".

How to achieve this?

$\endgroup$
4
  • $\begingroup$ No finished product, but I´d use a Button to import the data and generate an assignment cell that is output below that. $\endgroup$
    – Yves Klett
    Apr 11, 2013 at 7:10
  • $\begingroup$ I don't understand very well. You have written "The output cells are regenerated manually when the data files change". Do you confirm the word "manually" or is it a error ? $\endgroup$
    – andre314
    Apr 11, 2013 at 12:24
  • $\begingroup$ @andre Yes, the output cells with the data will be generated manually by evaluating the above "Input" cells with the Import statements as showed on the screenshot. $\endgroup$ Apr 11, 2013 at 12:58
  • $\begingroup$ @andre Probably the word "manually" is not the best choice. I just mean that the cells with Import statements must not be Initialization cells because I wish do not depend on the external files. At the same time, if the external files are changed it should be possible to regenerate the cells with the data by Importing the external files. So the output cells are generated automatically by Mathematica but manually in the sence that they are not Initialization cells. Sorry for confusion. $\endgroup$ Apr 11, 2013 at 13:05

5 Answers 5

4
$\begingroup$

I use buttons for this purpose. Imagine that your data is stored in a variable called data. Then you can do the following:

With[{i = Compress[data]}, 
  Button["Retrieve Data", data = Uncompress[i]; 
  Print[Style["Data has successfully been loaded into variable \"data\"",Blue,Italic,14]]]
]

This creates a button. When pressed, data is populated with your original data.

A warning: If you have a separate variable data already as part of your notebook, it will be overwritten.

An alternative:

Create initiialization cells somewhere in your notebook containing

data1 = <your data 1>;
data2 = <your data 2>;

To save space you can also do

data1 = Uncompress[Compress[<your data 1>]];

Then, highlight Compress[<your data 1>] and evaluate it in place (Ctrl-Shift-Enter)

$\endgroup$
5
  • $\begingroup$ You might also mention that the data is stored across sessions because of the way Button works. $\endgroup$
    – Yves Klett
    Apr 11, 2013 at 11:05
  • $\begingroup$ @YvesKlett That's right. In fact, you can select the cell containing the button, and press Ctrl-Shift-E to see the underlying Cell code. you will see the (compressed version of) your data there. That's the reason why you need the With[...] construct, without it, it would not work. $\endgroup$
    – Thomas
    Apr 11, 2013 at 11:11
  • $\begingroup$ @Thomas As I stated in the question, I wish to store data in readable form, as tables. I need not to compress it since the data have small size. $\endgroup$ Apr 11, 2013 at 11:56
  • $\begingroup$ @AlexeyPopkov you can simply print out data as well within the same With. $\endgroup$
    – Yves Klett
    Apr 11, 2013 at 13:04
  • $\begingroup$ I know it but the goal is to store data in obvious and readable form, not just to display them. And at the same time the cells with the data should be generated by Importing external files. $\endgroup$ Apr 11, 2013 at 13:16
2
$\begingroup$

I put in a defer because of your question here. Maybe you have symbols in your data and this will be useful, who knows. getData succesfully gets the data from the generated cell.

int = 1;
With[
  {string = "@#$kill me" <> ToString[int]}
  ,
  NotebookDelete[Cells[CellTags -> string]]; 
  CellPrint[
   ExpressionCell[
    Defer[{{1 + 1, 2}, {1 + 1, 2}, {1 + 1, 2}} // TableForm], 
    "Output", GeneratedCell -> True, Evaluatable -> True, 
    CellTags -> string]]
  ];

getDataHeldComplete[int_] := 
 With[
  {string = "@#$kill me" <> ToString[int]}
  ,
  MakeExpression @@ {NotebookRead[Cells[CellTags -> string][[1]]][[
     1]], StandardForm}
  ]

getData[int_] := getDataHeldComplete[1][[1]];

Then evaluate evaluate in a new cell

getDataHeldComplete[1]
data=getData[1]
$\endgroup$
2
$\begingroup$

You may use TableForm (you're already using this), CellTags, NotebookLocate, and NotebookRead to do this.

For your Import "Output" cell add a CellTag that will be your reference.

CellPrint[ExpressionCell[
  ImportString["1,2,3,4,5,6,7,8,9", "CSV"] // TableForm,
  "Output",
  CellTags -> "Data1"]]

This can be re-executed at any time going forward without having to manually adjust. Of course you will use Import and not ImportString.

The cell can be located in the notebook with NotebookLocate and its contents read with NotebookRead. When this is done you will see that the data is in a GridBox command. getCellTableFormData finds the first occurrence of this and returns the first argument; which is the data array.

getCellTableFormData[tag_String] :=
 Module[{},
  NotebookLocate[tag];
  With[{cell = NotebookRead[EvaluationNotebook[]]},
   ToExpression@First@Extract[cell, FirstPosition[cell, GridBox[__]]]
   ]
  ]

Now getCellTableFormData can be used to assign a variable to the data collected from the tagged cell of TableForm output.

dat = getCellTableFormData["Data1"]
(* {{"1", "2", "3", "4", "5", "6", "7", "8", "9"}} *)

Even after you close and reopen the notebook the tagged cell is still there. So all that is need is to run the getCellTtableFormData definition (initialisation cell or a package) so that the variables can be set from the tagged cell data.

Hope this helps.


Update

Here is a version that does not change the selected cell to the tagged cell when the data is assigned to the variable. It is also a more concise.

getCellTableFormData2[tag_String] :=
 With[{cell = NotebookRead@First@Cells[CellTags -> tag]},
  ToExpression@First@Extract[cell, FirstPosition[cell, GridBox[__]]]
  ]

It does not use NotebookLocate which sets the selected cell to the tagged cell.

$\endgroup$
6
  • $\begingroup$ I appreciate the idea but the NotebookLocate method opens closed CellGroup containing the Cell with the data what is not appropriate. Both the CellGroup with initialization Cell and the CellGroup with the data Cells should remain closed in the course of initialization. $\endgroup$ Sep 19, 2015 at 12:07
  • $\begingroup$ @AlexeyPopkov I've added another version that does not use NotebookLocate. See update. I added a 2 onto the name so don't forget to change the call. Maybe I should not have done that. $\endgroup$
    – Edmund
    Sep 19, 2015 at 12:09
  • $\begingroup$ (+1) Yes, the Cells method works as desired. I think that CellID is even better than CellTags for my purpose because two Cells with the same CellID are not allowed. The only problem is that the Cells function is absent in version 8 with which I still have to work because of insufficiently stable FrontEnds of versions 9 and 10. $\endgroup$ Sep 19, 2015 at 12:19
  • $\begingroup$ Both your solutions extract numbers as Strings. Instead of First@Extract[...] one can use ToExpression[cell[[1]]] for extraction numbers as numbers. $\endgroup$ Sep 19, 2015 at 12:31
  • 1
    $\begingroup$ CellID is only guaranteed to be unique when created by the Wolfram System. You are programming the notebook so you can be certain that the CellTags do not repeat for your data cells. Does CellID persist on closing and reopening the notebook? If they do then you might need to set negative number ids to be certain that the ids generated when executing the notebook don't overlap. $\endgroup$
    – Edmund
    Sep 19, 2015 at 12:33
1
$\begingroup$

Are you aware that you can write data= before your table and evaluate ? :

enter image description here

$\endgroup$
0
1
$\begingroup$

Here is the solution which I currently use. It isn't perfect but serves quite well for my purposes.

In the Notebook I define an auxiliary function which takes a variable where a data array is stored and prints an initialization Cell with two-dimensional human-readable representation of that array which will be assigned to the input variable on initialization:

ClearAll[printInitCell];
SetAttributes[printInitCell, HoldAll];
printInitCell[array_] := 
  CellPrint@Cell[
    BoxData[RowBox[{ToString[Unevaluated[array]], "=", 
       GridBox[Map[ToBoxes, array, {-1}], 
        GridBoxFrame -> {"Columns" -> {{True}}, "Rows" -> {{True}}}], ";"}]], "Output", 
    InitializationCell -> True, Evaluatable -> True];

Here is an example of use:

dataset1 = Prepend[RandomReal[1, {5, 3}], {"Head1", "Head2", "Head3"}];
printInitCell[dataset1]

screenshot

It is important that after initialization the variable dataset1 contains two-dimensional array which is not wrapped by Grid:

screenshot

The only drawback of this approach is that when I need to re-generate the initialization Cell, I have to remove the previous Cell manually.


UPDATE

With AutoIndent -> False and the variable placed above the data table the data Cells are more readable:

ClearAll[printInitCell];
SetAttributes[printInitCell, HoldAll];
printInitCell[array_] := 
  CellPrint@Cell[
    BoxData[RowBox[{ToString[Unevaluated[array]], " ", "=", " ", "\n", 
       GridBox[Map[ToBoxes, array, {-1}], 
        GridBoxFrame -> {"Columns" -> {{True}}, "Rows" -> {{True}}}, 
        BaseStyle -> (FontWeight -> Plain)], ";"}]], "Output", InitializationCell -> True,
     Evaluatable -> True, AutoIndent -> False, Background -> GrayLevel[0.95], 
    FontWeight -> Bold];

Now

screenshot


UPDATE 2

Here is Mathematica 8 - compatible solution which auto-overwrites previous version of the data Cell (if present) before printing of the new version.

At first, it checks whether the next Cell is a previous version by comparing the CellTags. If the latter is true it sets the Evaluatable -> False option what turns on automatic overwriting. Then it prints the new version and collapses the CellGroup hiding everything except to the newly printed initialization cell.

ClearAll[printInitCell];
SetAttributes[printInitCell, HoldAll];
printInitCell[var_] := With[{tag = ToString[Unevaluated[var]], nb = EvaluationNotebook[]},
   SelectionMove[nb, After, EvaluationCell, AutoScroll -> False];
   SelectionMove[nb, Next, Cell, AutoScroll -> False];
   If[CurrentValue[NotebookSelection[], CellTags] === {tag, "CellType=DataCell"}, 
    SetOptions[NotebookSelection[], Evaluatable -> False]];
   CellPrint@
    Cell[BoxData[
      RowBox[{ToString[Unevaluated[var]], " ", "=", " ", "\n", 
        GridBox[Map[ToBoxes, var, {-1}], 
         GridBoxFrame -> {"Columns" -> {{True}}, "Rows" -> {{True}}}, 
         BaseStyle -> (FontWeight -> Plain)], ";"}]], "Output", 
     InitializationCell -> True, Evaluatable -> True, AutoIndent -> False, 
     Background -> GrayLevel[0.95], FontWeight -> Bold, 
     CellTags -> {tag, "CellType=DataCell"}];
   SelectionMove[nb, Previous, Cell];
   FrontEndTokenExecute["OpenCloseGroup"]];
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.