Use tail() to preview the last rows in the current order. Inspect imported files and sort validated dates before selecting recent records.
tail() selects rows by position in their current order. It does not sort them or find the latest dates automatically.
import pandas as pd
from io import StringIO, BytesIO
students = pd.DataFrame({
"NAME": ["Ravi", "Raju", "Alex", "Ron", "King", "Jack"],
"ID": [1, 2, 3, 4, 5, 6],
"MATH": [80, 40, 70, 70, 60, 30],
"ENGLISH": [80, 70, 40, 50, 60, 30]
})
print(students)Expected output
NAME ID MATH ENGLISH
0 Ravi 1 80 80
1 Raju 2 40 70
2 Alex 3 70 40
3 Ron 4 70 50
4 King 5 60 60
5 Jack 6 30 30The default n is 5. Existing index labels and columns are preserved. A table with fewer rows returns all available rows.
print(students.tail())
assert students.tail()["ID"].tolist() == [2, 3, 4, 5, 6]Expected output
NAME ID MATH ENGLISH
1 Raju 2 40 70
2 Alex 3 70 40
3 Ron 4 70 50
4 King 5 60 60
5 Jack 6 30 30Pass an integer to choose a preview size. A Series caller returns a Series; a DataFrame caller returns a DataFrame.
print(students.tail(2))
print("Names:")
print(students["NAME"].tail(2))
assert students.tail(2)["NAME"].tolist() == ["King", "Jack"]Expected output
NAME ID MATH ENGLISH
4 King 5 60 60
5 Jack 6 30 30
Names:
4 King
5 Jack
Name: NAME, dtype: strtail(-1) keeps every row after the first. A negative n excludes the first absolute n rows, rather than selecting that many final rows.
print(students.tail(-1))
assert students.tail(-1)["ID"].tolist() == [2, 3, 4, 5, 6]
assert students.tail(-2)["ID"].tolist() == [3, 4, 5, 6]Expected output
NAME ID MATH ENGLISH
1 Raju 2 40 70
2 Alex 3 70 40
3 Ron 4 70 50
4 King 5 60 60
5 Jack 6 30 30tail(0) is empty but retains the columns. An oversized positive count returns the full table; a negative count whose magnitude exceeds the row count returns an empty table.
for n in [0, 20, -20]:
preview = students.tail(n)
print("n:", n, "Rows:", len(preview), "IDs:", preview["ID"].tolist())
assert students.tail(0).columns.equals(students.columns)
assert len(students.tail(20)) == 6Expected output
n: 0 Rows: 0 IDs: []
n: 20 Rows: 6 IDs: [1, 2, 3, 4, 5, 6]
n: -20 Rows: 0 IDs: []tail() works with nonsequential labels too. iloc uses positional slicing with square brackets. For n=0, tail(0) is empty while iloc[-0:] selects all rows, because -0 equals 0.
indexed = students.set_index("ID")
print(indexed.tail(2))
print("Matches positional slice:", indexed.tail(2).equals(indexed.iloc[-2:]))
assert indexed.tail(2).equals(indexed.iloc[-2:])
assert len(indexed.iloc[-0:]) == 6
assert len(indexed.tail(0)) == 0Expected output
NAME MATH ENGLISH
ID
5 King 60 60
6 Jack 30 30
Matches positional slice: TrueThe embedded CSV runs without a local file. Replace StringIO(csv_text) with your path or URL. A Windows path can use a raw string such as r"D:\my_data\student.csv". Calling tail() after reading still loads the full CSV. See read_csv().
csv_text = students.to_csv(index=False)
from_csv = pd.read_csv(StringIO(csv_text))
print(from_csv.tail(2))
print("Full shape:", from_csv.shape)
assert from_csv.shape == (6, 4)Expected output
NAME ID MATH ENGLISH
4 King 5 60 60
5 Jack 6 30 30
Full shape: (6, 4)The in-memory workbook demonstrates read_excel() without an upload. Excel needs an engine such as openpyxl; this example explains a missing dependency and lets the rest of the notebook continue. See read_excel() and to_excel().
try:
import openpyxl
except ImportError:
print("Excel example requires openpyxl; continue with the CSV examples.")
else:
workbook = BytesIO()
students.rename(columns={"ID": "id"}).to_excel(workbook, index=False, engine="openpyxl")
workbook.seek(0)
from_excel = pd.read_excel(workbook, index_col="id", engine="openpyxl")
print(from_excel.tail(2))
print("Full shape:", from_excel.shape)
assert from_excel.shape == (6, 3)Expected output
NAME MATH ENGLISH
id
5 King 60 60
6 Jack 30 30
Full shape: (6, 3)shape is an attribute, not a function. to_string() converts the last rows into display text. The count describes the full table, not only the preview.
summary = "Rows:" + str(students.shape[0]) + ",Columns:" + str(students.shape[1])
summary += "\n" + students.tail(2).to_string()
print(summary)
assert summary.startswith("Rows:6,Columns:4")Expected output
Rows:6,Columns:4
NAME ID MATH ENGLISH
4 King 5 60 60
5 Jack 6 30 30head() inspects the start; tail() inspects the end. sample() selects random rows. None of these previews proves that every record is valid.
print("First IDs:", students.head(2)["ID"].tolist())
print("Last IDs:", students.tail(2)["ID"].tolist())
print("Sample IDs:", students.sample(n=2, random_state=42)["ID"].tolist())Expected output
First IDs: [1, 2]
Last IDs: [5, 6]
Sample IDs: [1, 2]This synthetic sales table arrives out of date order and includes an invalid date. Parse dates, retain invalid records for review and sort known dates before taking a latest-order preview. Use order_id to break ties deterministically.
raw = pd.DataFrame({
"order_id": [101, 102, 103, 104, 105, 106],
"date": ["2026-09-05", "2026-09-01", "bad", "2026-09-03", "2026-09-05", "2026-09-02"],
"amount": [20, 10, 99, 15, 30, 12]
})
parsed = pd.to_datetime(raw["date"], errors="coerce")
review = raw.loc[parsed.isna()].copy()
valid = raw.loc[parsed.notna()].copy()
valid["date"] = parsed.loc[valid.index]
ordered = valid.sort_values(["date", "order_id"])
print("Original final IDs:", raw.tail(2)["order_id"].tolist())
print("Date review:")
print(review)
print("Ordered records:")
print(ordered)
assert review["order_id"].tolist() == [103]Expected output
Original final IDs: [105, 106]
Date review:
order_id date amount
2 103 bad 99
Ordered records:
order_id date amount
1 102 2026-09-01 10
5 106 2026-09-02 12
3 104 2026-09-03 15
0 101 2026-09-05 20
4 105 2026-09-05 30Sort ascending then tail(2) selects the final two records. The preview stays in ascending order; sort the selected rows descending if you want newest first. This selects two records, not two distinct dates. See sort_values() and to_datetime().
latest = ordered.tail(2)
print("Latest records:")
print(latest)
print("Newest first:")
print(latest.sort_values(["date", "order_id"], ascending=[False, False]))
assert latest["order_id"].tolist() == [101, 105]Expected output
Latest records:
order_id date amount
0 101 2026-09-05 20
4 105 2026-09-05 30
Newest first:
order_id date amount
4 105 2026-09-05 30
0 101 2026-09-05 20Latest-order revenue is 50; revenue for all five known-date orders is 87. The invalid-date row remains in review. Report the population behind each total. Continue with Data Cleaning and sum().
print("Known-date orders:", len(ordered), "Review rows:", len(review))
print("All known-date revenue:", ordered["amount"].sum())
print("Latest preview revenue:", latest["amount"].sum())
assert len(ordered) + len(review) == len(raw)
assert ordered["amount"].sum() == 87
assert latest["amount"].sum() == 50Expected output
Known-date orders: 5 Review rows: 1
All known-date revenue: 87
Latest preview revenue: 50Export ordered for all known-date records, or latest for an explicitly named latest-order report. Keep order_id as a column; reset disposable row labels only if needed. See Input and Output and reset_index().
print(latest.to_csv(index=False).rstrip())
print("Export rows:", len(latest))
assert len(latest) == 2
# Optional Colab exports:
# latest.to_csv("latest_two_orders.csv", index=False)
# ordered.to_csv("all_known_date_orders.csv", index=False)Expected output
order_id,date,amount
101,2026-09-05,20
105,2026-09-05,30
Export rows: 2Does tail() find recent dates? Only after a suitable sort. Does it reverse rows? No; selected rows retain their order. Does it change the source? No. Why are fewer rows returned? The source may be smaller than n. Why does tail(-2) return more than two rows? It excludes the first two. Does inspecting the end validate the whole file? No; check missing values, types and rules across the full table. Continue practising with shape and size, info(), text-match counts, student exercises and sample student files.
Create IDs 1, 2 and 3 with dates September 3, September 1 and September 2 of 2026 and amounts 30, 10 and 20. Sort by date, preview the latest two and calculate their total.
The latest two records are IDs 3 and 1 in ascending date order, with total 50.
practice = pd.DataFrame({
"id": [1, 2, 3],
"date": pd.to_datetime(["2026-09-03", "2026-09-01", "2026-09-02"]),
"amount": [30, 10, 20]
})
answer = practice.sort_values(["date", "id"]).tail(2)
print(answer)
print("Latest total:", answer["amount"].sum())
assert answer["id"].tolist() == [3, 1]
assert answer["amount"].sum() == 50Expected output
id date amount
2 3 2026-09-02 20
0 1 2026-09-03 30
Latest total: 50Open in Google Colab View on GitHub
Run the examples in order, change dates and preview sizes, then compare final rows with latest orders. Save your own copy to keep edits. All sample tables are included in the notebook.
Reference: Pandas DataFrame.tail documentation.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.