Pandas
DataFrame
List
Python
Data Conversion

Pandas DataFrame to List of Lists

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Converting a Pandas DataFrame to a list of lists is a common interoperability step when some other part of the program expects plain Python data structures. The conversion itself is easy, but the useful answer usually depends on whether you need the index, whether column order matters, and how specialized values such as timestamps or missing data should be represented.

The Standard Conversion

The usual solution is to convert the frame to a NumPy-style array and then call .tolist() on it.

python
1import pandas as pd
2
3df = pd.DataFrame(
4    {
5        "name": ["Ana", "Ben", "Cara"],
6        "score": [95, 88, 91],
7    }
8)
9
10rows = df.to_numpy().tolist()
11print(rows)

Output:

python
[['Ana', 95], ['Ben', 88], ['Cara', 91]]

Each inner list represents one row. The values appear in the current column order of the DataFrame.

You will also see df.values.tolist(). It often works the same way, but to_numpy() is clearer because it states the conversion step explicitly.

Column Order Is Not Just Cosmetic

A list of lists does not carry column names with it, so column order becomes part of the contract. If the receiving code expects [name, score], make that order explicit before converting.

python
1import pandas as pd
2
3df = pd.DataFrame(
4    {
5        "score": [95, 88],
6        "name": ["Ana", "Ben"],
7    }
8)
9
10rows = df[["name", "score"]].to_numpy().tolist()
11print(rows)

This matters after merges, column inserts, or refactoring. Assuming the frame still has the right order is a common source of subtle bugs.

The Index Is Excluded by Default

The nested list conversion only includes data columns. It does not include the DataFrame index.

python
1import pandas as pd
2
3df = pd.DataFrame(
4    {"score": [95, 88]},
5    index=["Ana", "Ben"],
6)
7
8print(df.to_numpy().tolist())

Output:

python
[[95], [88]]

If the index should become part of each row, convert it into a normal column first.

python
rows = df.reset_index().to_numpy().tolist()
print(rows)

That turns the original row labels into the first element of each inner list.

Handle Missing Values and Specialized Types Deliberately

Real-world frames often contain datetimes, booleans, missing values, or nullable extension dtypes. The conversion still works, but the output may not be the simple Python representation your downstream code expects.

python
1import pandas as pd
2
3df = pd.DataFrame(
4    {
5        "created_at": pd.to_datetime(["2024-01-01", "2024-01-02"]),
6        "score": [95, None],
7        "active": [True, False],
8    }
9)
10
11rows = df.to_numpy().tolist()
12print(rows)

That output may contain timestamp objects and a missing-value marker that another library cannot serialize cleanly. In those cases, normalize the data before conversion.

python
1prepared = df.copy()
2prepared["created_at"] = prepared["created_at"].dt.strftime("%Y-%m-%d")
3prepared["score"] = prepared["score"].fillna(0)
4
5rows = prepared.to_numpy().tolist()
6print(rows)

The best time to make values "plain Python friendly" is before the final conversion step.

Return Column Names Separately When Needed

Sometimes the consumer needs both the rows and the schema. A list of lists alone is not enough because the column labels are lost.

python
1import pandas as pd
2
3df = pd.DataFrame(
4    {
5        "name": ["Ana", "Ben"],
6        "score": [95, 88],
7    }
8)
9
10columns = df.columns.tolist()
11rows = df.to_numpy().tolist()
12
13print(columns)
14print(rows)

This pattern is common when building chart payloads, spreadsheet exports, or generic JSON structures.

Consider Whether You Need Full Materialization

to_numpy().tolist() builds the entire nested list in memory. That is appropriate when another function really expects a complete Python list, but it is unnecessary overhead if you only need to iterate through rows once.

For one-pass processing, itertuples() is often a better tool:

python
1import pandas as pd
2
3df = pd.DataFrame(
4    {
5        "name": ["Ana", "Ben"],
6        "score": [95, 88],
7    }
8)
9
10for row in df.itertuples(index=False, name=None):
11    print(list(row))

This keeps the code efficient and avoids materializing a large nested list that you never actually needed.

A Practical Rule of Thumb

In modern Pandas code, df.to_numpy().tolist() is the default answer. It is explicit, readable, and usually correct. The extra engineering work is in shaping the frame beforehand: deciding which columns to include, choosing their order, deciding whether the index matters, and converting special dtypes into a format that the receiving system can accept.

In other words, the bug is rarely in .tolist(). The bug is usually in the assumptions surrounding it.

Common Pitfalls

The most common mistake is forgetting that the index is excluded. If row labels matter, use reset_index() or export the index separately.

Another pitfall is assuming the current column order is stable. Be explicit when downstream code depends on a specific arrangement.

A third issue is ignoring timestamps, missing values, or extension dtypes. The conversion may succeed technically while still producing values the next system cannot handle well.

Finally, do not convert a very large DataFrame to a list of lists if you only need one pass over the rows. In that case, row iteration is usually a better fit.

Summary

  • Convert a DataFrame to a list of lists with df.to_numpy().tolist().
  • The output contains row values only, in the frame's current column order.
  • The index is excluded unless you turn it into a regular column first.
  • Normalize datetimes and missing values before conversion when the consumer expects plain values.
  • Use row iteration instead of full materialization when you do not truly need a nested list in memory.

Course illustration
Course illustration

All Rights Reserved.