PythonMastery
reference 3 min read · lesson 60 of 60 in Errors

AttributeError: 'DataFrame' object has no attribute 'append'

1 · The lesson

read

What this error means

Your code calls df.append(...), and that method no longer exists. It was deprecated in pandas 1.4 and removed in pandas 2.0 (April 2023), together with Series.append. The code isn't wrong so much as old — it was written for pandas 1.x, and most tutorials and Stack Overflow answers from before 2023 still use it. The replacement is pd.concat, or, better in most cases, not appending at all.

When you see it

python
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    sales = sales.append({"region": "North", "units": 12}, ignore_index=True)
            ^^^^^^^^^^^^
  File ".../pandas/core/generic.py", line 6318, in __getattr__
    return object.__getattribute__(self, name)
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
AttributeError: 'DataFrame' object has no attribute 'append'. Did you mean: '_append'?

The Series version:

python
AttributeError: 'Series' object has no attribute 'append'. Did you mean: '_append'?

Do not take that "Did you mean" hint. Python 3.13 is just pointing at the closest-named attribute; _append is a private pandas internal with a leading underscore precisely so you won't depend on it. It can change or vanish in any release.

Why it happens

df.append never added to the DataFrame in place. It copied the whole thing, plus the new row, every single time. Inside a loop that's quadratic: the thousandth row copies 999 rows to add one. The pandas team removed it rather than keep a method that looked like list.append but behaved nothing like it.

python
import pandas as pd

sales = pd.DataFrame({"region": ["South"], "units": [8]})
sales = sales.append({"region": "North", "units": 12}, ignore_index=True)   # AttributeError

How to fix it

Building a table row by row? Collect plain dicts in a list, create the DataFrame once. This is the fix for nine out of ten cases, and it's far faster than any form of appending:

python
import pandas as pd

orders = [
    ("Ada", "keyboard", 2, 49.99),
    ("Grace", "monitor", 1, 189.00),
    ("Linus", "mouse", 3, 19.50),
]

rows = []
for customer, item, qty, price in orders:
    rows.append({"customer": customer, "item": item, "qty": qty, "total": qty * price})

df = pd.DataFrame(rows)
print(df)

rows.append there is an ordinary Python list append, which is cheap. The expensive work happens once, at pd.DataFrame(rows).

Joining DataFrames you already have? pd.concat. Pass a list of frames; use ignore_index=True if the old index labels don't mean anything:

python
import pandas as pd

january = pd.DataFrame({"region": ["North", "South"], "units": [120, 95]})
february = pd.DataFrame({"region": ["North", "South"], "units": [134, 88]})

sales = pd.concat([january, february], ignore_index=True)
print(sales)

If you're loading many monthly CSVs in a loop, put each DataFrame in a list and call pd.concat once after the loop — not once per file, which has the same quadratic problem as the old append.

Adding exactly one row to an existing frame? Two options. With a default RangeIndex (0, 1, 2…), df.loc[len(df)] = ... writes a new row at the next label:

python
import pandas as pd

sales = pd.DataFrame({"region": ["South"], "units": [8]})
sales.loc[len(sales)] = ["North", 12]
print(sales)

This only works safely when the index really is 0 to n-1. After filtering or set_index, len(df) might be a label that already exists, and you'd overwrite that row without a word. In that case use concat with a one-row frame:

python
import pandas as pd

sales = pd.DataFrame({"region": ["South"], "units": [8]})
new_row = pd.DataFrame([{"region": "North", "units": 12}])
sales = pd.concat([sales, new_row], ignore_index=True)
print(sales)

Series.append → pd.concat too.

python
import pandas as pd

week_one = pd.Series([310, 295, 340], name="visits")
week_two = pd.Series([360, 372], name="visits")
visits = pd.concat([week_one, week_two], ignore_index=True)
print(visits.tolist())          # [310, 295, 340, 360, 372]

Pinning pandas<2 in requirements.txt to make the old code run is a stopgap, not a fix. You'll lose two years of bug fixes and the next library upgrade will fight you on it.

When you'd actually see this in real code

  • A scraper or API loop written in 2021 that does df = df.append(row, ignore_index=True) for every result.
  • A Jupyter notebook from an old course that suddenly breaks after a fresh pip install pandas.
  • A Colab or Kaggle notebook — both moved to pandas 2.x, and older shared notebooks stopped running.
  • A results-collecting loop in a model evaluation script: results = results.append({"model": name, "score": score}, ...).

See Also

Found this useful? Share it with a friend who’s learning Python.