Pandas: Add Rows with concat() Instead of append()

Add lists, dictionaries, Series and DataFrame rows with concat(). Filter incoming records, manage indexes and add columns.

Replace DataFrame.append() with concat() 🔝

DataFrame.append() was removed in Pandas 2.0. Keep this page bookmarked: its original row-adding tasks now use pd.concat(). Pass a list of DataFrames; convert dictionaries and lists into rows first. concat() returns a new object, so assign its result. All marks below are synthetic practice data.

Create the original student sample 🔝

The original Ravi, Raju and Alex data is retained. Later examples start from this unchanged base.
import pandas as pd
df = pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"], "ID": [1, 2, 3],
    "MATH": [30, 40, 50], "ENGLISH": [20, 30, 40]})
print(df)
assert df.shape == (3, 4)

Expected output

   NAME  ID  MATH  ENGLISH
0  Ravi   1    30       20
1  Raju   2    40       30
2  Alex   3    50       40

Add one list as a row 🔝

Supply column names when turning a positional list into a one-row DataFrame. By default concat preserves labels, including repeated index 0.
my_list = ["New 1", 7, 66, 56]
new_row = pd.DataFrame([my_list], columns=df.columns)
added = pd.concat([df, new_row])
print(added)
assert added.index.tolist() == [0, 1, 2, 0]
assert len(df) == 3

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
0  New 1   7    66       56

Add several rows and reset the index 🔝

ignore_index=True creates a new RangeIndex. It does not deduplicate ID values or records.
rows = [["New 1", 7, 66, 56], ["New 2", 8, 63, 59], ["New 3", 9, 69, 69]]
incoming = pd.DataFrame(rows, columns=df.columns)
combined = pd.concat([df, incoming], ignore_index=True)
print(combined)
assert combined.index.tolist() == list(range(6))

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  New 1   7    66       56
4  New 2   8    63       59
5  New 3   9    69       69

Add a dictionary 🔝

Wrap the original New 5 dictionary in a list to create one record.
record = {"NAME": "New 5", "ID": 10, "MATH": 78, "ENGLISH": 80}
from_dict = pd.concat([df, pd.DataFrame([record])], ignore_index=True)
print(from_dict)
assert from_dict.iloc[-1]["NAME"] == "New 5"

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  New 5  10    78       80

Add a Series as a row 🔝

A Series must be oriented as a row. Mixed values may initially have object dtype; this example converts numeric columns explicitly.
record_series = pd.Series({"NAME": "New 5", "ID": 10, "MATH": 78, "ENGLISH": 80})
series_row = record_series.to_frame().T.astype(df.dtypes.to_dict())
from_series = pd.concat([df, series_row], ignore_index=True)
print(from_series)
pd.testing.assert_frame_equal(from_series, from_dict)

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  New 5  10    78       80

Add a row with loc safely 🔝

The original loc[len(df)] example works with this consecutive index. loc uses labels: on an irregular index len(df) may already exist and overwrite a row. Reset a working copy first or use concat.
working = df.reset_index(drop=True).copy()
working.loc[len(working)] = ["new 6", 10, 80, 88]
print(working)
assert len(working) == 4 and len(df) == 3

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  new 6  10    80       88

Conditionally add rows in a loop 🔝

Retain the original marks-total rule: at least 160. Collect qualifying rows, then concatenate once instead of repeatedly growing a DataFrame.
candidates = [["new 4", 4, 80, 88], ["new 5", 5, 81, 84], ["new 6", 6, 76, 78]]
accepted_rows = []
for row in candidates:
    if row[2] + row[3] >= 160:
        accepted_rows.append(row)
accepted = pd.DataFrame(accepted_rows, columns=df.columns)
loop_result = pd.concat([df, accepted], ignore_index=True)
print(loop_result)
assert loop_result["NAME"].tolist()[-2:] == ["new 4", "new 5"]

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  new 4   4    80       88
4  new 5   5    81       84

Filter incoming rows before concatenating 🔝

Use the original row-wise sum and its inverse to retain both accepted and rejected groups. See row filtering.
candidate_df = pd.DataFrame(candidates, columns=df.columns)
qualifies = candidate_df[["MATH", "ENGLISH"]].sum(axis=1) >= 160
filtered = pd.concat([df, candidate_df.loc[qualifies]], ignore_index=True)
inverse = pd.concat([df, candidate_df.loc[~qualifies]], ignore_index=True)
print(filtered)
print("Inverse selection:")
print(inverse)
pd.testing.assert_frame_equal(filtered, loop_result)
assert inverse.iloc[-1]["NAME"] == "new 6"

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  new 4   4    80       88
4  new 5   5    81       84
Inverse selection:
    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  new 6   6    76       78

Add a column 🔝

Column assignment is separate from adding rows. The original SCIENCE marks remain unchanged.
with_science = df.copy()
with_science["SCIENCE"] = [34, 35, 36]
print(with_science)
assert "SCIENCE" not in df.columns

Expected output

   NAME  ID  MATH  ENGLISH  SCIENCE
0  Ravi   1    30       20       34
1  Raju   2    40       30       35
2  Alex   3    50       40       36

Understand column alignment and sort 🔝

concat aligns column names. Outer joins retain all columns and use missing values where a column is absent; inner joins keep only shared columns. sort=True orders column names.
partial = pd.DataFrame([{"NAME": "New 7", "ID": 11}])
outer = pd.concat([df, partial], ignore_index=True, sort=False)
inner = pd.concat([df, partial], ignore_index=True, join="inner")
sorted_columns = pd.concat([df, partial], ignore_index=True, sort=True)
print(outer)
print("Inner columns:", inner.columns.tolist())
print("Sorted columns:", sorted_columns.columns.tolist())
assert pd.isna(outer.iloc[-1]["MATH"])
assert inner.columns.tolist() == ["NAME", "ID"]

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1  30.0     20.0
1   Raju   2  40.0     30.0
2   Alex   3  50.0     40.0
3  New 7  11   NaN      NaN
Inner columns: ['NAME', 'ID']
Sorted columns: ['ENGLISH', 'ID', 'MATH', 'NAME']

Check index integrity and business IDs separately 🔝

verify_integrity checks the concatenated axis labels, not an ID column. With ignore_index=True the new row labels are already unique.
try:
    pd.concat([df, new_row], verify_integrity=True)
except ValueError:
    print("Duplicate index labels rejected")
else:
    raise AssertionError("Expected an overlapping index error")
repeat = pd.concat([df, df.iloc[[0]]], ignore_index=True, verify_integrity=True)
print("Repeated IDs:", repeat.loc[repeat["ID"].duplicated(), "ID"].tolist())
assert repeat["ID"].duplicated().sum() == 1

Expected output

Duplicate index labels rejected
Repeated IDs: [1]

Validate an incoming batch 🔝

For this synthetic policy, accept total marks of at least 160 and reject IDs already present. Keep a review table. In a real import also validate missing values, numeric ranges and duplicates within the incoming batch.
batch = pd.DataFrame([["Duplicate Ravi", 1, 90, 90], ["new 8", 8, 85, 85],
    ["new 9", 9, 60, 70]], columns=df.columns)
valid = batch[["MATH", "ENGLISH"]].sum(axis=1).ge(160) & ~batch["ID"].isin(df["ID"])
review = batch.loc[~valid].copy()
result = pd.concat([df, batch.loc[valid]], ignore_index=True)
print(result)
print("Review IDs:", review["ID"].tolist())
assert result["ID"].is_unique and len(result) == 4
assert len(review) == 2

Expected output

    NAME  ID  MATH  ENGLISH
0   Ravi   1    30       20
1   Raju   2    40       30
2   Alex   3    50       40
3  new 8   8    85       85
Review IDs: [1, 9]

Common mistakes 🔝

Do not call removed df.append(), pass a bare dictionary to concat, assume ignore_index removes duplicate records, or use loc[len(df)] on an arbitrary index. Repeated concatenation in a loop is costly; collect records and concatenate once. Use merge() when matching records by keys rather than stacking rows.

Exercise: add two dictionary records 🔝

Add New 10 (ID 10, MATH 70, ENGLISH 80) and New 11 (ID 11, MATH 60, ENGLISH 90), reset the result index and keep df unchanged. Verify five rows and unique IDs.

Exercise solution 🔝

Construct the incoming batch once and assign the returned concatenation.
records = [{"NAME": "New 10", "ID": 10, "MATH": 70, "ENGLISH": 80},
    {"NAME": "New 11", "ID": 11, "MATH": 60, "ENGLISH": 90}]
answer = pd.concat([df, pd.DataFrame(records)], ignore_index=True)
print(answer)
assert len(answer) == 5 and answer["ID"].is_unique
assert len(df) == 3 and answer.index.tolist() == list(range(5))

Expected output

     NAME  ID  MATH  ENGLISH
0    Ravi   1    30       20
1    Raju   2    40       30
2    Alex   3    50       40
3  New 10  10    70       80
4  New 11  11    60       90

Practice in Google Colab 🔝

Open in Google Colab View on GitHub

Run examples in order, inspect added rows and try the exercise. Save a copy to keep edits.

Reference: Pandas concat() documentation.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer