QuantConnect/Lean · error · AssertionError
Could not unstack df. Columns: {', '.join(df.columns)} | {co
Error message
Could not unstack df. Columns: {', '.join(df.columns)} | {column} What it means
Guard in assert_history_index that the expected column exists in the dataframe before unstacking/accessing it. The helper later does df[column].unstack(level=0) and df.at[(...), column], so the column must be present. The assertion fires when the history frame's columns do not include the requested column name, indicating the unstack/indexing strategy will not work for this data.
Source
Thrown at Algorithm.Python/PandasDataFrameHistoryAlgorithm.py:99
# we can use these TradeBars to initialize indicators or perform other math
self.spy_daily_sma.reset()
for index, trade_bar in trade_bar_history.loc["SPY"].iterrows():
self.spy_daily_sma.update(index, trade_bar["close"])
# we can loop over the return values from these functions and we'll get Quandl data
# this can be used in much the same way as the trade_bar_history above
self.spy_daily_sma.reset()
for index, quandl in quandl_history.loc["CHRIS/CME_SP1"].iterrows():
self.spy_daily_sma.update(index, quandl["settle"])
self.set_holdings(self.eur, 1)
def assert_history_index(self, df, column, expected, ticker, symbol):
if df.empty:
raise AssertionError(f"Empty history data frame for {symbol}")
if column not in df:
raise AssertionError(f"Could not unstack df. Columns: {', '.join(df.columns)} | {column}")
value = df.iat[0,0]
df2 = df.xs(df.index.get_level_values('time')[0], level='time')
df3 = df[column].unstack(level=0)
try:
# str(Symbol.ID)
self.assert_history_count(f"df.iloc[0]", df.iloc[0], len(df.columns))
self.assert_history_count(f"df.loc[str({symbol.id})]", df.loc[str(symbol.id)], expected)
self.assert_history_count(f"df.xs(str({symbol.id}))", df.xs(str(symbol.id)), expected)
self.assert_history_count(f"df.at[(str({symbol.id}),), '{column}']", list(df.at[(str(symbol.id),), column]), expected)
self.assert_history_count(f"df2.loc[str({symbol.id})]", df2.loc[str(symbol.id)], len(df2.columns))
self.assert_history_count(f"df3[str({symbol.id})]", df3[str(symbol.id)], expected)
self.assert_history_count(f"df3.get(str({symbol.id}))", df3.get(str(symbol.id)), expected)
# str(Symbol)
self.assert_history_count(f"df.loc[str({symbol})]", df.loc[str(symbol)], expected)View on GitHub (pinned to d2c3659f87)
Solutions
- Log df.columns to see the actual available columns and pick the correct name.
- If using custom data, verify the data type's properties and value_column_name still produce the expected column.
- After a Lean/pandas upgrade, re-check the column-generation contract for that data type and update the expected column name.
- Confirm the dataframe is at the expected index level (symbol/time) so column lookup is valid.
Example fix
# before
if column not in df:
raise AssertionError(f"Could not unstack df. Columns: {', '.join(df.columns)} | {column}")
# after (report columns in a stable, sorted form and hint at the mismatch)
if column not in df.columns:
raise AssertionError(
f"Could not unstack df; '{column}' missing. "
f"Available columns: {', '.join(map(str, sorted(df.columns)))}") Defensive patterns
Strategy: validation
Validate before calling
# Verify the column exists (and report actual columns) before unstacking
def assert_history_index(self, df, column, expected, ticker, symbol):
if column not in df.columns:
self.debug(f"'{column}' not in {sorted(map(str, df.columns))} for {symbol}")
return Type guard
def has_column(df, column):
"""True when the dataframe exposes the requested column."""
return df is not None and column in df.columns Prevention
- Log df.columns to confirm available field names before accessing.
- Keep custom-data value_column_name consistent with the asserted column.
- Re-check column contracts after a Lean/pandas upgrade.
When it happens
Trigger: column not in df.columns for a history dataframe. This happens when the data type's properties do not include the expected column (e.g. asking for 'settle' on data that does not expose it), when the dataframe is multi-indexed and the column lives at a different level, or when Lean's PandasData column generation for that type changed/renamed the field.
Common situations: A custom data type (e.g. QuandlFuture expecting 'Settle') no longer exposes the column after a rename or a change to value_column_name. The history frame came back with a different column layout after a Lean or pandas upgrade. The caller passed the wrong column name for the requested data type.
Related errors
- Unexpected columns in SPY tick history
- Empty history data frame for {symbol}
- History call failed: {e}
- {symbols}, {symbol.id}, {symbol}, {ticker}. {e}
- {method_call} expected {expected}, but received {count}
AI-assisted analysis of QuantConnect/Lean@d2c3659f87 (2026-08-13).
Data as JSON: /api/errors/6a23ec75782e6935.
Report an issue: GitHub.