The point of this answer is to show that there is a straight forward way to do this with simple iteration and tools from the standard library.
Often times we perform many transformations on a Pandas DataFrame where each transformation invokes the construction of a new Pandas object. At times this can be an intuitive progression and make perfect sense. However, there are times when we forget that we can use simpler tools. I believe this is one of those times. My answer still uses Pandas in that I use the itertuples
method.
from collections import defaultdict
d = defaultdict(dict)
for a, b, c in df.itertuples(index=False):
d[b][a] = c
d = dict(d)
d
{'t': {'a1': 1, 'a2': 2}, 'd': {'a3': 3, 'a4': 4}}
Slight alternative. Since the tuples we are iterating over are named tuples, we can access each element by the name of the column it represents.
from collections import defaultdict
d = defaultdict(dict)
for t in df.itertuples():
d[t.ColB][t.ColA] = t.ColC
d = dict(d)
d
{'t': {'a1': 1, 'a2': 2}, 'd': {'a3': 3, 'a4': 4}}