Looking at the answer of @happyfish the code below is an overkill...
Here we find the positions corresponding in the values:
pos = Position[vals, _?NumberQ, \[Infinity]]
Here we find the dimensions corresponding to the positions:
posToDims = MapThread[Part, {dims, #}] & /@ pos
Here we make Trie records from the dimensions and the values:
t = MapThread[
Function[{d, p}, Append[d, vals[[Sequence @@ p]]]], {posToDims, pos}, 1]
Here we make the trie:
res = makeTreeAssoc[t]
Finally, we remove the redundant <|{}->{}|>
(coming from Shifrin's original implementation):
res /. <|k_ -> <|{} -> {}|>|> :> k
(* {"New York City" -> <|"Terrace house" -> <|"Two bed" -> \
<|"2016" -> 0., "2017" -> 633.25|>,
"Three bed" -> <|"2016" -> 642.96, "2017" -> 677.34|>|>,
"Apartment" -> <|"Two bed" -> <|"2016" -> 591.56,
"2017" -> 632.45|>,
"Three bed" -> <|"2016" -> 634.9, "2017" -> 715.6|>|>|>} *)
Definitions
Here is a small change of the original definitions are given in this answer:
ClearAll[makeTreeAssoc];
makeTreeAssoc[wrds_ /; MemberQ[wrds, {}]] :=
Prepend[makeTreeAssoc[DeleteCases[wrds, {}]], {} -> {}];
makeTreeAssoc[wrds_] :=
Reap[If[# =!= {}, Sow[Rest[#], First@#]] & /@
wrds, _, #1 -> Association@makeTreeAssoc[#2] &][[2]]
(I was tempted to give an answer with one of my Trie packages, but luckily Leonid Shifrin has provided a concise implementation of Tries using core Association functionalities.)