Your dict is already a table — from_dict() just needs to know which way it points.
The dict you got from that API response is one orient= away from being a DataFrame — most people rebuild it the slow way.
pd.DataFrame.from_dict() turns a dictionary into a DataFrame. With the default orient='columns', each key becomes a column and the values (lists, arrays, or Series) become that column's data — all columns must be the same length. With orient='index', the outer keys become row labels and inner dicts become rows; pass columns=[...] to name them at construction time. In pandas 1.4+ there's orient='tight', which packs index, columns, and data into one dict so you can round-trip a frame (MultiIndex included) losslessly and stash it as JSON.
Half of real-world pandas work starts from a dict: a parsed API response, an aggregated result, hand-typed test fixtures, a config-driven report. The plain constructor pd.DataFrame(d) only handles one of these shapes cleanly, so people reshuffle with .T or pd.DataFrame(list(d.values())) and get mangled dtypes. from_dict() names the direction explicitly and validates it — wrong orientation fails fast instead of silently transposing mixed types into object columns. It's also the only constructor that reads pandas' own tight format, which makes it the missing to_dict() counterpart for serializing frames.
A colleague mails you dict-shaped sales data. Build the report table directly — keys become columns:
>>> sales = pd.DataFrame.from_dict({
... "city": ["Vienna", "Graz", "Linz", "Salzburg"],
... "units": [120, 85, 60, 95],
... "revenue": [4800, 3060, 1980, 3800],
... })
>>> print(sales)
city units revenue
0 Vienna 120 4800
1 Graz 85 3060
2 Linz 60 1980
3 Salzburg 95 3800
>>> type(sales["units"])
<class 'pandas.Series'>
orient='columns' is the default: values must be equal-length sequences, and dtype is inferred per column — units stays int64, not one object column like a naive transpose would give you.
Now the dict is row-shaped: weekday → {city → units}. Flip it with orient='index' and name the columns up front:>>> week = pd.DataFrame.from_dict(
... {"Mon": {"Vienna": 31, "Graz": 12},
... "Tue": {"Vienna": 28, "Graz": 15}},
... orient="index", columns=["Vienna", "Graz"],
... )
>>> print(week)
Vienna Graz
Mon 31 12
Tue 28 15
orient='index' + columns= is the one-shot version of 'build then rename' — inner keys become the index, and the columns list replaces the default integer labels immediately.
A report script returns nested-column names as tuples. Rebuild it and prove the tight round-trip is lossless:
>>> raw = {"stat": ["mean", "max"],
... ("revenue", "EUR"): [2955.0, 4800.0],
... ("units", "pcs"): [90, 120]}
>>> df = pd.DataFrame.from_dict(raw)
>>> df.columns.tolist()
['stat', ('revenue', 'EUR'), ('units', 'pcs')]
>>> tight = df.to_dict(orient="tight")
>>> print(tight["columns"])
['stat', ('revenue', 'EUR'), ('units', 'pcs')]
>>> pd.DataFrame.from_dict(tight, orient="tight").equals(df)
True
orient='tight' (pandas 1.4+) is the only lossless dict serialization for MultiIndex frames — index, columns, and data travel together, so equals() comes back True. Regular to_dict('list') flattens the tuples.
Sensor readings with a target dtype for the whole frame:
>>> sensors = pd.DataFrame.from_dict(
... {"temp_c": [21.5, 19.8], "hum_pct": [48, 52]}, dtype="float32"
... )
>>> print(sensors.dtypes)
temp_c float32
hum_pct float32
dtype: object
dtype= applies to the entire frame at construction — handy when downstream code expects float32 before any arithmetic happens.
| Flag | Meaning |
|---|---|
data (first argument) | A dict; with orient='columns' values must be equal-length sequences, with orient='index' values are inner dicts/sequences keyed by column. |
orient='columns' (default) | Keys become column names, values become column data. The shape you want when the dict is column-major. |
orient='index' | Outer keys become row labels, inner keys become column names; pairs with columns= to set labels in the same call. |
orient='tight' | Expects a dict with keys index, columns, data — pandas' lossless serialization format, round-trips MultiIndex exactly (pandas 1.4+). |
columns=[...] | Set column labels at construction (only with orient='index'/'tight'); avoids a separate rename step. |
dtype=... | Cast the whole frame to one dtype on the way in, e.g. dtype='float32' for compact sensor data. |
from_dict appeared as a classmethod on DataFrame in the 0.7.0 release (Wes McKinney's pre-1.0 API build-out, the same year pandas got its first tag on GitHub).
orient='tight' was added to from_dict and to_dict together (issue 4889) — a decade after the method itself, closing the serialization loop for frames with MultiIndex.
from_dict() constructs through the internal _from_arrays-style path: with orient='columns' it extracts values from each sequence and builds one column per key, inferring dtype per column; with orient='index' it walks outer keys as rows. Because dict keys are unique, from_dict() never needs the duplicate-label handling the main constructor does. The tight format stores index, columns, and data as three separate entries — mirroring how pandas' internal block manager thinks about a frame — which is exactly why it can rebuild a MultiIndex losslessly while the other two orientations cannot.