Convert a column with tolist() or table rows with to_numpy().tolist(). Decide how headers, identifiers, dtypes and missing values should be represented.
A Series has tolist() and to_list(). A DataFrame has no direct tolist() method: use to_numpy().tolist() for nested rows or to_dict() for labelled records.
import pandas as pd
from io import BytesIO
df = 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(df)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 30Each inner list follows the current column order. Column names and index labels are not included. The values attribute also supports this conversion, but to_numpy() makes the conversion explicit.
rows = df.to_numpy().tolist()
legacy_rows = df.values.tolist()
print(type(rows).__name__)
print(rows)
assert rows == legacy_rows
assert rows[0] == ["Ravi", 1, 80, 80]Expected output
list
[['Ravi', 1, 80, 80], ['Raju', 2, 40, 70], ['Alex', 3, 70, 40], ['Ron', 4, 70, 50], ['King', 5, 60, 60], ['Jack', 6, 30, 30]]Convert columns to a list before inserting them. Inserting df.columns directly inserts an Index object rather than a header list. list.insert() adds the header row.
table = df.to_numpy().tolist()
table.insert(0, df.columns.tolist())
print(table)
assert isinstance(table[0], list)
assert table[0] == ["NAME", "ID", "MATH", "ENGLISH"]Expected output
[['NAME', 'ID', 'MATH', 'ENGLISH'], ['Ravi', 1, 80, 80], ['Raju', 2, 40, 70], ['Alex', 3, 70, 40], ['Ron', 4, 70, 50], ['King', 5, 60, 60], ['Jack', 6, 30, 30]]Series.tolist() returns values without labels. For a list of distinct values use unique() first when deduplication is part of your rule.
names = df["NAME"].tolist()
print(names)
print("Equivalent alias:", df["NAME"].to_list())
assert names == df["NAME"].to_list()Expected output
['Ravi', 'Raju', 'Alex', 'Ron', 'King', 'Jack']
Equivalent alias: ['Ravi', 'Raju', 'Alex', 'Ron', 'King', 'Jack']Index.tolist(), list(index) and the original values-based alternatives produce the same labels for this simple integer index. A MultiIndex produces tuples representing complete labels.
indexed = df.set_index("ID")
labels = indexed.index.tolist()
print(labels)
assert labels == indexed.index.values.tolist()
assert labels == list(indexed.index)
assert labels == list(indexed.index.values)
multi = df.set_index(["NAME", "ID"])
print("MultiIndex labels:", multi.index.tolist())Expected output
[1, 2, 3, 4, 5, 6]
MultiIndex labels: [('Ravi', 1), ('Raju', 2), ('Alex', 3), ('Ron', 4), ('King', 5), ('Jack', 6)]If an ID exists only in the index, reset it to a column before converting. Otherwise the row lists lose the identifier. See reset_index().
indexed = df.set_index("ID")
rows_with_id = indexed.reset_index().to_numpy().tolist()
print(rows_with_id)
assert rows_with_id[0][0] == 1Expected output
[[1, 'Ravi', 80, 80], [2, 'Raju', 40, 70], [3, 'Alex', 70, 40], [4, 'Ron', 70, 50], [5, 'King', 60, 60], [6, 'Jack', 30, 30]]Choose columns and row filters before conversion. This makes the positional meaning of each field explicit. See loc and row filtering.
selected = df.loc[df["MATH"].ge(70), ["ID", "NAME", "MATH"]]
result = selected.to_numpy().tolist()
print(result)
assert [row[0] for row in result] == [1, 3, 4]Expected output
[[1, 'Ravi', 80], [3, 'Alex', 70], [4, 'Ron', 70]]A NumPy array has a common dtype. Numeric columns can be coerced, for example integers becoming floats when combined with a float column. For preserving per-column scalar types, construct rows through itertuples() or use labelled records.
mixed = pd.DataFrame({"id": [1, 2], "amount": [5.5, 8.0]})
array_rows = mixed.to_numpy().tolist()
tuple_rows = [list(row) for row in mixed.itertuples(index=False, name=None)]
print("Array rows:", array_rows)
print("Tuple rows:", tuple_rows)
assert isinstance(array_rows[0][0], float)
assert isinstance(tuple_rows[0][0], int)Expected output
Array rows: [[1.0, 5.5], [2.0, 8.0]]
Tuple rows: [[1, 5.5], [2, 8.0]]Lists can contain Pandas missing scalars or NaN. If a consumer expects None, replace missing values explicitly in an object-typed table. This does not make timestamps or every other type JSON-compatible.
nullable = pd.DataFrame({"id": [1, 2], "quantity": pd.Series([3, pd.NA], dtype="Int64")})
print("Series list:", nullable["quantity"].tolist())
objects = nullable.astype(object)
normalized = objects.where(nullable.notna(), None)
rows = normalized.to_numpy().tolist()
print("None policy:", rows)
assert rows[1][1] is None
assert nullable["quantity"].isna().sum() == 1Expected output
Series list: [3, <NA>]
None policy: [[1, 3], [2, None]]This in-memory colour workbook illustrates the Name column from the Excel example. For your file, pass its path to read_excel(). Excel needs openpyxl; other cells can continue if it is unavailable.
colours = pd.DataFrame({"Name": ["Red", "Green", "Blue"], "Hex": ["#FF0000", "#008000", "#0000FF"]})
try:
import openpyxl
except ImportError:
print("Excel example requires openpyxl; embedded choices:", colours["Name"].tolist())
else:
workbook = BytesIO()
colours.to_excel(workbook, index=False, engine="openpyxl")
workbook.seek(0)
imported = pd.read_excel(workbook, engine="openpyxl")
choices = imported["Name"].tolist()
print(choices)
assert choices == ["Red", "Green", "Blue"]Expected output
['Red', 'Green', 'Blue']This synthetic product list contains repeated labels, whitespace and missing values. Define the case policy before merging labels; here case-insensitive comparison is intended. Keep a raw source for review.
raw_products = pd.Series([" Pen ", "pen", "Book", "", None, "Pencil"], dtype="string")
clean = raw_products.str.strip().str.casefold()
clean = clean.mask(clean.eq(""))
choices = sorted(clean.dropna().unique().tolist())
print("Choices:", choices)
print("Missing rows:", clean.isna().sum())
assert choices == ["book", "pen", "pencil"]Expected output
Choices: ['book', 'pen', 'pencil']
Missing rows: 2Keep a header list and one value list per row. Format values for the report separately from stored numeric data. Follow the DataFrame PDF-table tutorial to pass the prepared rows to a PDF library.
sales = pd.DataFrame({"order_id": [101, 102, 103], "product": ["Pen", "Book", "Pencil"], "amount": [5.25, 20.0, 8.5]})
display = sales.copy()
display["amount"] = display["amount"].map(lambda value: f"{value:.2f}")
pdf_rows = [display.columns.tolist()] + display.to_numpy().tolist()
print(pdf_rows)
assert len(pdf_rows) == len(sales) + 1
assert sales["amount"].sum() == 33.75Expected output
[['order_id', 'product', 'amount'], [101, 'Pen', '5.25'], [102, 'Book', '20.00'], [103, 'Pencil', '8.50']]to_dict(orient="records") preserves column labels in each record. to_dict(orient="list") produces one list per column. Pick the representation expected by the receiving application.
records = sales.to_dict(orient="records")
by_column = sales.to_dict(orient="list")
print(records)
print(by_column)
assert records[0]["order_id"] == 101
assert by_column["product"] == ["Pen", "Book", "Pencil"]Expected output
[{'order_id': 101, 'product': 'Pen', 'amount': 5.25}, {'order_id': 102, 'product': 'Book', 'amount': 20.0}, {'order_id': 103, 'product': 'Pencil', 'amount': 8.5}]
{'order_id': [101, 102, 103], 'product': ['Pen', 'Book', 'Pencil'], 'amount': [5.25, 20.0, 8.5]}Supply columns explicitly when rebuilding a DataFrame. This recovers the values in this example, but does not automatically restore original extension dtypes, category definitions or an index.
table = [sales.columns.tolist()] + sales.to_numpy().tolist()
restored = pd.DataFrame(table[1:], columns=table[0])
print(restored)
assert restored.equals(sales)Expected output
order_id product amount
0 101 Pen 5.25
1 102 Book 20.00
2 103 Pencil 8.50Why does df.tolist() fail? DataFrame has no direct tolist() method. Are headers included? No; add columns.tolist() when required. Is the index included? No; reset meaningful labels first. Does conversion change the table? It returns a new list; changing simple scalar items in it does not update the table. Nested mutable Python objects may still share references. Will large conversion use memory? It materializes the selected values as Python lists; select only the required data. Continue with describe(), head(), to_string() and rename().
Create a list of lists containing ID, NAME and MATH for students below 50 in Maths. Add a real header list and predict the number of rows including the header.
Raju and Jack match. The result has one header and two data rows.
selected = df.loc[df["MATH"].lt(50), ["ID", "NAME", "MATH"]]
answer = [selected.columns.tolist()] + selected.to_numpy().tolist()
print(answer)
assert len(answer) == 3
assert [row[0] for row in answer[1:]] == [2, 6]Expected output
[['ID', 'NAME', 'MATH'], [2, 'Raju', 40], [6, 'Jack', 30]]Open in Google Colab View on GitHub
Run the examples in order, change selected fields and missing values, then inspect flat lists and nested rows. Save your own copy to keep edits. All sample tables are included in the notebook.
Reference: Pandas Series.to_list 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.