Use to_string() to create readable table text. Choose displayed columns, headers, missing markers and numeric precision, then write a clearly scoped report.
Watch: DataFrame text output with to_string()
to_string() produces console-friendly table text, not CSV, JSON or Python source. Without buf it returns a string. Use print() to show its line breaks.
import pandas as pd
from io import StringIO, BytesIO
import sqlite3
df = pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"], "ID": [1, 2, 3],
"MATH": [30, 40, 50], "ENGLISH": [20, 30, 40]})
text = df.to_string()
print(text)
print("Return type:", type(text).__name__)
assert isinstance(text, str)Expected output
NAME ID MATH ENGLISH
0 Ravi 1 30 20
1 Raju 2 40 30
2 Alex 3 50 40
Return type: strPassing buf writes the table and returns None. The buffer demonstration avoids creating a file during Run All. Uncomment the file call in Colab to save a UTF-8 report. Windows paths can use a raw string such as r"D:\my_data\my_file.txt".
buffer = StringIO()
returned = df.to_string(buf=buffer, index=False)
print(buffer.getvalue())
print("Return value:", returned)
assert returned is None
assert buffer.getvalue() == df.to_string(index=False)
# Optional file output:
# df.to_string(buf="student_report.txt", index=False, encoding="utf-8")Expected output
NAME ID MATH ENGLISH
Ravi 1 30 20
Raju 2 40 30
Alex 3 50 40
Return value: Nonecolumns controls displayed fields; index=False hides row labels. These formatting choices do not change the source table.
print(df.to_string(columns=["NAME", "ID"], index=False))
assert df.columns.tolist() == ["NAME", "ID", "MATH", "ENGLISH"]Expected output
NAME ID
Ravi 1
Raju 2
Alex 3header=False hides column headings. A header alias list must match the displayed columns. The original column labels remain unchanged.
print("Aliases:")
print(df.to_string(header=["NAME1", "ID2", "MATH3", "ENGLISH4"]))
print("No header:")
print(df.to_string(header=False, index=False))
assert df.columns[0] == "NAME"Expected output
Aliases:
NAME1 ID2 MATH3 ENGLISH4
0 Ravi 1 30 20
1 Raju 2 40 30
2 Alex 3 50 40
No header:
Ravi 1 30 20
Raju 2 40 30
Alex 3 50 40col_space controls minimum column width, not a strict maximum. justify affects header alignment; actual value alignment also depends on dtype. A long value can still make a column wide.
print(df.to_string(col_space=10, index=False))
print("Centered headers:")
print(df.to_string(justify="center", index=False))Expected output
NAME ID MATH ENGLISH
Ravi 1 30 20
Raju 2 40 30
Alex 3 50 40
Centered headers:
NAME ID MATH ENGLISH
Ravi 1 30 20
Raju 2 40 30
Alex 3 50 40na_rep changes the displayed missing marker. The stored missing values remain missing. See the Data Cleaning hub for value treatment rather than display formatting.
measurements = pd.DataFrame({"product": ["Pen", "Book", "Bag"], "amount": [5.25, None, 30.0]})
print(measurements.to_string(index=False, na_rep="Missing"))
assert measurements["amount"].isna().sum() == 1Expected output
product amount
Pen 5.25
Book Missing
Bag 30.00float_format applies to floating-point values; per-column formatters provide more specific formatting. Rounded display does not round the stored values. Choose precision appropriate to the report.
print(measurements.to_string(index=False, na_rep="Missing", float_format=lambda value: f"{value:.2f}"))
print(df.to_string(index=False, formatters={"ID": lambda value: f"S{value:03d}"}))
assert measurements.loc[0, "amount"] == 5.25
assert df["ID"].tolist() == [1, 2, 3]Expected output
product amount
Pen 5.25
Book Missing
Bag 30.00
NAME ID MATH ENGLISH
Ravi S001 30 20
Raju S002 40 30
Alex S003 50 40max_rows, max_cols and max_colwidth limit the representation, not the dataset. Ellipses mean the display is incomplete. Use a deliberately selected preview for large tables and retain full-table totals separately.
wide = pd.DataFrame({"id": range(1, 9), "description": ["Long product description"] * 8,
"amount": range(10, 18), "status": ["paid"] * 8})
print(wide.to_string(index=False, max_rows=4, max_cols=3, max_colwidth=12, show_dimensions=True))
assert wide.shape == (8, 4)Expected output
id ... status
1 ... paid
2 ... paid
.. ... ...
7 ... paid
8 ... paid
[8 rows x 4 columns]StringIO makes this import runnable without an uploaded file. For your CSV, supply its path or URL. to_string() works on the resulting DataFrame regardless of its source. See read_csv().
csv_text = df.to_csv(index=False)
imported = pd.read_csv(StringIO(csv_text))
print(imported.to_string(index=False))
assert imported.equals(df)Expected output
NAME ID MATH ENGLISH
Ravi 1 30 20
Raju 2 40 30
Alex 3 50 40This in-memory workbook uses read_excel(). Excel needs an engine such as openpyxl; the notebook can continue if it is unavailable. Replace the buffer with your workbook path for your own data.
try:
import openpyxl
except ImportError:
print("Excel example requires openpyxl; continue with other sources.")
else:
workbook = BytesIO()
df.to_excel(workbook, index=False, engine="openpyxl")
workbook.seek(0)
imported = pd.read_excel(workbook, engine="openpyxl")
print(imported.to_string(index=False))
assert imported.shape == df.shapeExpected output
NAME ID MATH ENGLISH
Ravi 1 30 20
Raju 2 40 30
Alex 3 50 40SQLite provides a self-contained database example. For MySQL, use an existing authorized SQLAlchemy connection and keep credentials out of the tutorial or shared notebook. The formatting step is the same.
with sqlite3.connect(":memory:") as connection:
df.to_sql("student", connection, index=False)
imported = pd.read_sql_query("SELECT NAME, ID, MATH, ENGLISH FROM student ORDER BY ID LIMIT 10", connection)
print(imported.to_string(index=False))
assert imported.shape == (3, 4)
# Optional MySQL adaptation using your configured SQLAlchemy engine:
# with engine.connect() as connection:
# imported = pd.read_sql_query("SELECT * FROM student ORDER BY id LIMIT 10", connection)
# print(imported.to_string(index=False))Expected output
NAME ID MATH ENGLISH
Ravi 1 30 20
Raju 2 40 30
Alex 3 50 40This synthetic sales report includes the population behind its total. A two-row preview is labelled as a preview, not presented as the whole dataset. Use head() and sum() for the two different tasks.
sales = pd.DataFrame({"order_id": [101, 102, 103, 104],
"product": ["Pen", "Book", "Bag", "Pencil"], "amount": [5.25, 20.0, 30.0, 8.5]})
preview = sales.head(2)
report = f"Sales report: {len(sales)} orders; revenue {sales['amount'].sum():.2f}\n"
report += f"Preview: first {len(preview)} orders\n"
report += preview.to_string(index=False, float_format=lambda value: f"{value:.2f}")
print(report)
assert sales["amount"].sum() == 63.75
assert len(preview) == 2Expected output
Sales report: 4 orders; revenue 63.75
Preview: first 2 orders
order_id product amount
101 Pen 5.25
102 Book 20.00to_string() is designed for people reading a table. Do not parse its aligned whitespace as a reliable interchange format. Use to_csv(), to_json() or to_excel() when another program needs structured records.
csv_output = sales.to_csv(index=False)
loaded = pd.read_csv(StringIO(csv_output))
print(csv_output.rstrip())
print("Same values:", loaded.equals(sales))
assert loaded.equals(sales)
# Optional report file:
# from pathlib import Path
# Path("sales_preview.txt").write_text(report, encoding="utf-8")Expected output
order_id,product,amount
101,Pen,5.25
102,Book,20.0
103,Bag,30.0
104,Pencil,8.5
Same values: TrueWhy are backslash-n characters visible? A string representation may escape line breaks; print the string. Does hiding the index remove it? No; this changes display only. Does float_format change stored amounts? No. Can this create a UTF-8 file? Yes; use a file buffer or write the returned text with an explicit encoding. Does truncation reduce memory used by the source? No. Should I print a whole large table? Select a bounded preview and calculate full-table measures separately. Continue with read_json(), list conversion and the student SQL sample.
Show the first three sales orders with order_id, product and amount, without the index and with amounts displayed to two decimal places. Confirm that the full sales table still has four rows.
The display selection affects only the report text.
answer = sales.head(3).to_string(columns=["order_id", "product", "amount"],
index=False, float_format=lambda value: f"{value:.2f}")
print(answer)
assert "5.25" in answer
assert len(sales) == 4Expected output
order_id product amount
101 Pen 5.25
102 Book 20.00
103 Bag 30.00Open in Google Colab View on GitHub
Run the examples in order, change displayed columns and formatting options, then compare text with the stored values. Save your own copy to keep edits. All sample tables are included in the notebook.
Reference: Pandas DataFrame.to_string 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.