TL;DR: DataFrame.append was removed in pandas 2.0 after a long deprecation. Replace df.append(row) with pd.concat([df, new_row_df], ignore_index=True). If the old code appended inside a loop, collect the frames in a list and concat once at the end, which is also dramatically faster.

## The error

```text
"AttributeError: 'DataFrame' object has no attribute 'append'" after pandas 2 upgrade
```

## Steps to fix

1. Find every call: search the codebase for .append( on DataFrame objects (Series.append was removed too).
   Expected: You have the full list of call sites to change.

2. Replace single appends: df = df.append(new_row, ignore_index=True) becomes df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True).
   Expected: The snippet runs and the frame grows by one row as before.

3. Fix loops: code that appends per iteration becomes frames = [] ... frames.append(chunk) ... df = pd.concat(frames, ignore_index=True).
   Expected: The loop runs faster and produces the identical final frame.

4. Run the affected tests and compare row counts and dtypes against the old output.
   Expected: Tests pass with matching shapes and dtypes.

## Use this when

- Pandas 2 upgrade breaks code with 'DataFrame' object has no attribute 'append'
- An agent bumped pandas and ETL scripts fail on row-append calls
- You are migrating a pandas 1.x codebase and need the mechanical replacement

## Not for this skill when

- Performance problems with concat itself on huge frames, which need chunking or a different engine
- Merge or join logic errors, which are separate APIs
- ArrowDtype or nullable-dtype changes in pandas 2, which are different migration issues

## Variant phrasings

- pandas 2 Series.append removed
- DataFrame has no attribute append pandas 2.0
- replacement for df.append in pandas 2

## Why it happens

DataFrame.append was deprecated in pandas 1.4 because it encouraged the anti-pattern of growing a frame row by row, copying the whole frame on every call. pandas 2.0 completed the removal as scheduled. pd.concat is the intended API: it makes the copy cost explicit and lets you combine many frames in one operation, which is why the loop-collect-then-concat rewrite is both the fix and a speedup.

## Edge cases

- pd.concat preserves the original indexes unless you pass ignore_index=True; forgetting it is the most common follow-up bug.
- Appending a dict directly no longer works; wrap it as pd.DataFrame([row_dict]) first.
- Check dtypes after concat: mixing frames with different dtypes can upcast columns (for example int to float) where append used to coerce the same way, so verify downstream code.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_vnmx3R7FiVkFJZvCZGpyHQ
