When you open an unfamiliar data file, the first thing you need to know about each column is: what is this? Are these dates? dollar amounts? county codes? people’s names? guess_data_type() answers that question. You hand it a column of values and it hands back a $route — its best read of what the column is, expressed as a path through the package’s type ontology.
guess_data_type(c("03/15/2019", "12/31/2020", "01/01/2021"))$route[
c("data_type", "semantic_family", "semantic_type", "format_style",
"depth", "confidence")]
#> $data_type
#> [1] "temporal"
#>
#> $semantic_family
#> [1] "date"
#>
#> $semantic_type
#> [1] "calendar"
#>
#> $format_style
#> [1] "mdy_slash"
#>
#> $depth
#> [1] 4
#>
#> $confidence
#> [1] 1The rest of this guide shows how to read that answer, why it is shaped as a decision tree, and — most importantly — why it is allowed to stop early.
Typing is a walk down a decision tree
The answer is a path, four levels deep, from coarse to fine. Typing a column means starting at the top with a value and walking down, answering one narrowing question at each step. Here is the full walk for a column of dates like 03/15/2019:
Read it left to right, top to bottom:
-
data_type— how should these be treated? Not words, not categories, not quantities, but points in time. →temporal. -
semantic_family— what broad kind of time? Not a clock time, not a span. →date. -
semantic_type— what exactly does it mean? A plain calendar date, not a timestamp with a clock attached. →calendar. -
format_style— how is it written? Month first, with slashes. →mdy_slash.
The branches not taken (number, time, timestamp, ymd_dash …) are real alternatives the engine weighed and ruled out. The value column always confirms it:
guess_data_type(c("03/15/2019", "12/31/2020"))$route[
c("data_type", "semantic_family", "semantic_type", "format_style", "depth")]
#> $data_type
#> [1] "temporal"
#>
#> $semantic_family
#> [1] "date"
#>
#> $semantic_type
#> [1] "calendar"
#>
#> $format_style
#> [1] "mdy_slash"
#>
#> $depth
#> [1] 4The bottom of the tree has two facets: how it’s written and what it’s in
The leaf level is really two questions, because they are genuinely different. Take a column of dollar amounts:
-
format_styleis how the value is written. The same amount could arrive as$2,300.00,2300, or17,298$. Style controls how you read it in and write it back out. -
unit_typeis what the value is measured in.2300could be dollars or euros; a temperature could be °C or °F;2019could be a calendar year or a fiscal year. The unit does not change how the value is written, but it changes what it means — and whether a conversion is even possible.
guess_data_type(c("$2,300.00", "17,298$", "$-45"))$route[
c("data_type", "semantic_family", "semantic_type", "format_style")]
#> $data_type
#> [1] "number"
#>
#> $semantic_family
#> [1] "currency"
#>
#> $semantic_type
#> [1] "usd"
#>
#> $format_style
#> [1] "usd"Reaching either facet counts as getting to the bottom. A plain date has a writing style but no meaningful unit; a bare temperature reading has a unit that matters more than its style.
Graceful failure: get the type right first, then go as deep as you can
Real data is messy, and often the values simply do not say enough to reach a leaf. This is the single most important thing to understand about guess_data_type(): it stops at the deepest level it can actually defend, and refuses to force a guess.
Consider a column of bare whole numbers — 0, 1, 42, 17:
guess_data_type(c("0", "1", "42", "17", "3"))$route[
c("data_type", "semantic_family", "semantic_type", "format_style", "depth")]
#> $data_type
#> [1] "number"
#>
#> $semantic_family
#> [1] "quantity"
#>
#> $semantic_type
#> [1] "count"
#>
#> $format_style
#> [1] NA
#>
#> $depth
#> [1] 3The engine is certain these are a number, and narrows confidently to a count — but a bare integer has no distinctive written form, so it stops at depth 3 rather than invent a leaf. A column whose values are even less decisive stops sooner still:
# plain decimals: clearly numeric, but nothing says what kind
guess_data_type(c("3.7", "8.1", "2.9", "5.4"))$route[
c("data_type", "semantic_family", "semantic_type", "depth")]
#> $data_type
#> [1] "number"
#>
#> $semantic_family
#> [1] NA
#>
#> $semantic_type
#> [1] NA
#>
#> $depth
#> [1] 1The depth field is exactly how far the walk got:
0 unknown 2 the family 4 the leaf (format_style and/or unit_type)
1 the data_type 3 the specific type
Why is this the right behavior? Because the two levels do different jobs, and they fail differently:
-
data_typeis the load-bearing answer. It decides how a column is profiled and treated: anumberyou can sum and average; anidentifieryou must leave alone; atemporalyou can put on a timeline. Getting this level right is the primary measure of success, and it is almost always reachable. - The leaf is the bonus. Reaching it unlocks the exact recipe for cleaning and safely storing the column (see Formatting Syntax). It is the goal, but missing it only means falling back to the safe, general rule for the level you did reach — never corruption.
So the worst case is never a confidently wrong answer; it is an honest “this far and no further,” left for a column name or a data dictionary to settle. A column name, when you pass one, is used only as a tie-breaker between leaves the values already support — it never overrides the evidence in the values.
The type:family:type triplet, at a glance
Across a handful of columns, here is what guess_data_type() returns — the data_type : semantic_family : semantic_type triplet, the leaf format, and how far down each one reached:
| example | data_type : family : type | format_style | depth |
|---|---|---|---|
| 03/15/2019, 12/31/2020 | temporal : date : calendar | mdy_slash | 4 |
| 2019-03-15, 2020-12-31 | temporal : date : calendar | ymd_dash | 4 |
| , $-45 | number : currency : usd | usd | 4 |
| California, TX, Ohio | categorical : mutually_exclusive : geographic | us_state | 4 |
| 06037, 36061, 48201 | identifier : id : geographic | geographic | 4 |
| 0, 1, 42, 17 | number : quantity : count | — | 3 |
| 3.7, 8.1, 2.9 | number : NA : NA | — | 1 |
The clean, unambiguous columns reach depth 4 and name a leaf; the bare numbers stop short but still get their data_type right. That is graceful failure working exactly as designed.
Where to go next
-
The Ontology — the full catalog of data types, families, and semantic types that
guess_data_type()walks through, and the detector used to spot each one. -
Formatting Syntax — once a column reaches a leaf,
fx_stabilize()runs the leaf’s recipe to clean and standardize the values. That is the second half of the workflow. ```