# How to make datasets display correctly?

NB: The problem illustrated below is truly ubiquitous. I hope the examples given below are sufficiently different to demonstrate this fact, and to discourage answers that hinge on the details of the examples.

Example 1:

columnSpecs = Module[
{
numberOfRows = 4
, columnNames = {"a", "b", "c"}
, numberOfColumns
, columns
}

, numberOfColumns = Length[columnNames]
; SeedRandom[0]
; columns = Partition[ RandomSample[ Range[numberOfRows * numberOfColumns]], numberOfRows]
];

ugly = Dataset[columnSpecs][Transpose];
nice = Dataset[ Transpose[columnSpecs, AllowedHeads -> All]];


Now, the datasets ugly and nice have exactly the same content:

Normal[ugly] == Normal[nice]
(* True *)


but they "look" different:

{ugly, nice}


(ugly: "What does nice have that I don't?" Life is so unfair.)

Example 2:

In this case, the RHS expressions in the definitions of ugly and nice have exactly the same form, but differ in content.

ugly = Dataset[{<|"a" -> 11, "b" ->  2 , "c" -> 12|>}];
nice = Dataset[{<|"a" -> 11, "b" -> "2", "c" -> 12|>}];

{ugly, nice}


Bottom line: Datasets get displayed inconsistently.

## How can one canonicalize "ugly" Datasets so that they look "nice"?

As pointed out at the beginning of this post, in my experience, "ugly" Datasets arise in a very wide variety of ways, and therefore it would be a fool's errand to attempt to solve this problem at the point of generation of these "ugly" Datasets (there would be too many do's and don't's to remember). For instance, one could avoid the "ugly" Dataset of Example 1 by using Dataset[columnSpecs][Transpose[#, AllowedHeads->All]&] instead of Dataset[columnSpecs][Transpose], but this solution is very specific to the particularities of Transpose, and is therefore useless for the case shown in Example 2.

Hence, I'm much more interested in ways to canonicalize (i.e. fix) the "ugly" Datasets after-the-fact (the "remedial" strategy) than in ways to avoid them in the first place (the "preventive" strategy). One can hope the remedial strategy would be the one more likely to yield the more generally applicable solution.

The reason for the difference between ugly and nice is in the (hidden) data type that Dataset assigns them:

ugly = Dataset[{<|"a" -> 11, "b" -> 2, "c" -> 12|>}];
nice = Dataset[{<|"a" -> 11, "b" -> "2", "c" -> 12|>}];
<< Dataset;
<< TypeSystem;

In[43]:= ugly // GetType

(* Vector[Assoc[Atom[Enumeration["a", "b", "c"]], Atom[Integer], 3], 1] *)

In[44]:= nice // GetType

(* Vector[Struct[{"a", "b", "c"}, {Atom[Integer], Atom[String], Atom[Integer]}], 1] *)


Dataset bases its typesetting on these types. In this case, there isn't a good typesetting rule implemented for the Vector[Assoc[Atom[Enumeration[...],...]]] type (this is a bug that'll be fixed in a future version of Mathematica).

In order to bypass this, we can assign ugly a type manually, via Dataset's hidden second argument, so:

notugly = Dataset[{<|"a" -> 11, "b" -> 2, "c" -> 12|>},
Vector[Struct[{"a", "b", "c"}, {Atom[Integer], Atom[Integer], Atom[Integer]}], 1]]


This is possible because the same underlying data can conform to several different types, one of which could have a nice typesetting rule, as notugly does: