pandas-dev/pandas · error · UndefinedVariableError

name '{key}' is not defined

Error message

name '{key}' is not defined

What it means

UndefinedVariableError raised by Scope.resolve() in pandas/core/computation/scope.py:245 with is_local=False/None. It means a bare identifier referenced inside DataFrame.query()/DataFrame.eval()/pd.eval() was not found in any of: the DataFrame columns (resolvers), the calling frame's scope, or the temporaries produced while parsing indexing expressions. The message format 'name {key!r} is not defined' mirrors Python's own NameError phrasing, because UndefinedVariableError subclasses NameError.

Source

Thrown at pandas/core/computation/scope.py:245

            if is_local:
                return self.scope[key]

            # not a local variable so check in resolvers if we have them
            if self.has_resolvers:
                return self.resolvers[key]

            # if we're here that means that we have no locals and we also have
            # no resolvers
            assert not is_local and not self.has_resolvers
            return self.scope[key]
        except KeyError:
            try:
                # last ditch effort we look in temporaries
                # these are created when parsing indexing expressions
                # e.g., df[df > 0]
                return self.temps[key]
            except KeyError as err:
                raise UndefinedVariableError(key, is_local) from err

    def swapkey(self, old_key: str, new_key: str, new_value=None) -> None:
        """
        Replace a variable name, with a potentially new value.

        Parameters
        ----------
        old_key : str
            Current variable name to replace
        new_key : str
            New variable name to replace `old_key` with
        new_value : object
            Value to be replaced along with the possible renaming
        """
        if self.has_resolvers:
            maps = self.resolvers.maps + self.scope.maps
        else:
            maps = self.scope.maps

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Check the identifier against df.columns (e.g. assert 'x' in df.columns) before calling query/eval when the string is dynamic.
  2. If the name should be a Python variable, prefix it with '@' (df.query('A > @x')); if it should be a column, correct the spelling or add the column.
  3. When calling pd.eval with explicit namespaces, ensure the name is present in local_dict or global_dict (do not pass empty dicts unless you intend to hide the namespace).
  4. For dynamic/user-supplied expressions, parse with ast and validate every Name node against an allow-list of columns + known locals before evaluation.

Example fix

// before
df.query("A > x")  # UndefinedVariableError: name 'x' is not defined

// after
x = 5
df.query("A > @x")  # reference the local explicitly
Defensive patterns

Strategy: validation

Validate before calling

import ast

def validate_query_names(expr: str, df, extra_locals: dict | None = None) -> list[str]:
    """Return list of undefined bare names (excluding @locals and callables)."""
    tree = ast.parse(expr, mode='eval')
    cols = set(df.columns)
    known = set((extra_locals or {}).keys())
    problems = []
    for n in ast.walk(tree):
        if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Load):
            if n.id not in cols and n.id not in known:
                problems.append(n.id)
    return problems

# usage
bad = validate_query_names('A > x', df)
assert not bad, f'undefined names: {bad}'
df.query('A > x')

Type guard

def names_are_resolvable(expr: str, df, local_ns: dict) -> bool:
    import ast
    tree = ast.parse(expr, mode='eval')
    cols = set(df.columns)
    for n in ast.walk(tree):
        if isinstance(n, ast.Name):
            if n.id not in cols and n.id not in local_ns:
                return False
    return True

Try / catch

import re
from pandas.errors import UndefinedVariableError

try:
    result = df.query(expr)
except UndefinedVariableError as e:
    # NOTE: UndefinedVariableError does not expose .name/.is_local as attributes;
    # the offending identifier only lives in the message text.
    m = re.search(r"name '([^']+)' is not defined$", str(e))
    bad = m.group(1) if m else str(e)
    raise ValueError(f'query references unknown column/variable {bad!r}; columns={list(df.columns)}') from e

Prevention

When it happens

Trigger: df.query('A > x') where 'A' is a column but 'x' is neither a column nor a variable in the calling scope; df.eval('col1 + col2') where 'col2' is misspelled or absent; pd.eval('foo + 1', local_dict={}, global_dict={}) with an emptied namespace (GH 47084); referencing a builtin like sin inside query (query does not pick up builtins: df.query('sin > 5')).

Common situations: Renaming a DataFrame column but forgetting to update query/eval strings; dynamic generation of query strings from user input where a column name is missing; passing local_dict/global_dict explicitly to pd.eval and accidentally excluding the needed name; copy-pasted query expressions from a notebook into a function where the referenced local no longer exists; expecting Python builtins (sin, cos, abs) to resolve inside query.

Related errors


AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07). Data as JSON: /api/errors/050accb1a98ab6f7. Report an issue: GitHub.