diff --git a/python/CHANGELOG.rst b/python/CHANGELOG.rst
index da36514186..1a00e54f7a 100644
--- a/python/CHANGELOG.rst
+++ b/python/CHANGELOG.rst
@@ -85,9 +85,11 @@
``pack_untracked_polytomies`` allows large polytomies involving untracked samples to
be summarised as a dotted line (:user:`hyanwong`, :issue:`3011` :pr:`3010`, :pr:`3012`)
-
- Added a ``title`` parameter to ``.draw_svg()`` methods (:user:`hyanwong`, :pr:`3015`)
+- Add comma separation to all display numbers. (:user:`benjeffery`, :issue:`3017`, :pr:`3018`)
+
+
--------------------
[0.5.8] - 2024-06-27
--------------------
diff --git a/python/tests/test_highlevel.py b/python/tests/test_highlevel.py
index ce1bda61a2..e560fbfe40 100644
--- a/python/tests/test_highlevel.py
+++ b/python/tests/test_highlevel.py
@@ -39,7 +39,6 @@
import random
import re
import tempfile
-import textwrap
import unittest
import uuid as _uuid
import warnings
@@ -2191,7 +2190,7 @@ def test_html_repr(self, ts):
# Parse to check valid
ElementTree.fromstring(html)
assert len(html) > 5000
- assert f"
Trees | {ts.num_trees} |
" in html
+ assert f"Trees | {ts.num_trees:,} |
" in html
assert f"Time Units | {ts.time_units} |
" in html
for table in ts.tables.table_name_map:
assert f"{table.capitalize()} | " in html
@@ -3759,32 +3758,29 @@ def test_complex_mutations(self):
def test_str(self, ts_fixture):
t = ts_fixture.first()
assert isinstance(str(t), str)
- assert re.match(
- textwrap.dedent(
- r"""
- ╔═════════════════════════════╗
- ║Tree ║
- ╠═══════════════════╤═════════╣
- ║Index │ 0║
- ╟───────────────────┼─────────╢
- ║Interval │ 0-1 \(1\)║
- ╟───────────────────┼─────────╢
- ║Roots │[0-9 ]*║
- ╟───────────────────┼─────────╢
- ║Nodes │[0-9 ]*║
- ╟───────────────────┼─────────╢
- ║Sites │[0-9 ]*║
- ╟───────────────────┼─────────╢
- ║Mutations │[0-9 ]*║
- ╟───────────────────┼─────────╢
- ║Total Branch Length│[0-9\. ]*║
- ╚═══════════════════╧═════════╝
- """[
- 1:
- ]
- ),
- str(t),
+ pattern = re.compile(
+ r"""
+ ╔═+╗\s*
+ ║Tree.*?║\s*
+ ╠═+╤═+╣\s*
+ ║Index.*?│\s*\d+║\s*
+ ╟─+┼─+╢\s*
+ ║Interval.*?│\s*\d+-\d+\s*\(\d+\)║\s*
+ ╟─+┼─+╢\s*
+ ║Roots.*?│\s*\d+║\s*
+ ╟─+┼─+╢\s*
+ ║Nodes.*?│\s*\d+║\s*
+ ╟─+┼─+╢\s*
+ ║Sites.*?│\s*\d+║\s*
+ ╟─+┼─+╢\s*
+ ║Mutations.*?│\s*\d+║\s*
+ ╟─+┼─+╢\s*
+ ║Total\s*Branch\s*Length.*?│\s*[\d,]+\.\d+║\s*
+ ╚═+╧═+╝\s*
+ """,
+ re.VERBOSE | re.DOTALL,
)
+ assert pattern.search(str(t))
def test_html_repr(self, ts_fixture):
html = ts_fixture.first()._repr_html_()
diff --git a/python/tests/test_tables.py b/python/tests/test_tables.py
index 19fd0f104d..3b1bd50bad 100644
--- a/python/tests/test_tables.py
+++ b/python/tests/test_tables.py
@@ -697,7 +697,7 @@ def test_str_pos_time_integer(self):
table.set_columns(**input_data)
_, rows = table._text_header_and_rows()
for row in rows:
- assert f"{identifiable_integers[0]}" in row
+ assert f"{identifiable_integers[0]:,}" in row
assert f"{identifiable_floats[0]:.8f}" not in row
def test_repr_html(self):
diff --git a/python/tskit/genotypes.py b/python/tskit/genotypes.py
index 239e135777..cf82ca8902 100644
--- a/python/tskit/genotypes.py
+++ b/python/tskit/genotypes.py
@@ -1,7 +1,7 @@
#
# MIT License
#
-# Copyright (c) 2018-2023 Tskit Developers
+# Copyright (c) 2018-2024 Tskit Developers
# Copyright (c) 2015-2018 University of Oxford
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -311,16 +311,16 @@ def __str__(self) -> str:
freqs = self.frequencies()
rows = (
[
- ["Site id", str(site_id)],
- ["Site position", str(site_position)],
- ["Number of samples", str(len(self.samples))],
- ["Number of alleles", str(self.num_alleles)],
+ ["Site id", f"{site_id:,}"],
+ ["Site position", f"{site_position:,}"],
+ ["Number of samples", f"{len(self.samples):,}"],
+ ["Number of alleles", f"{self.num_alleles:,}"],
]
+ [
[
- f"""Samples with allele {'missing' if k is None
- else "'" + k + "'"}""",
- f"{counts[k]} ({freqs[k] * 100:.2g}%)",
+ f"Samples with allele "
+ f"""{'missing' if k is None else "'" + k + "'"}""",
+ f"{counts[k]:,} ({freqs[k] * 100:.2g}%)",
]
for k in self.alleles
]
diff --git a/python/tskit/tables.py b/python/tskit/tables.py
index 0aade18c66..d873cf0462 100644
--- a/python/tskit/tables.py
+++ b/python/tskit/tables.py
@@ -864,13 +864,13 @@ def _text_header_and_rows(self, limit=None):
row_indexes = util.truncate_rows(self.num_rows, limit)
for j in row_indexes:
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
row = self[j]
location_str = ", ".join(map(str, row.location))
- parents_str = ", ".join(map(str, row.parents))
+ parents_str = ", ".join([f"{p:,}" for p in row.parents])
rows.append(
- "{}\t{}\t{}\t{}\t{}".format(
+ "{:,}\t{}\t{}\t{}\t{}".format(
j,
row.flags,
location_str,
@@ -1139,10 +1139,10 @@ def _text_header_and_rows(self, limit=None):
for j in row_indexes:
row = self[j]
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
rows.append(
- "{}\t{}\t{}\t{}\t{:.{dp}f}\t{}".format(
+ "{:,}\t{}\t{:,}\t{:,}\t{:,.{dp}f}\t{}".format(
j,
row.flags,
row.population,
@@ -1332,11 +1332,11 @@ def _text_header_and_rows(self, limit=None):
decimal_places = 0 if self._columns_all_integer("left", "right") else 8
for j in row_indexes:
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
row = self[j]
rows.append(
- "{}\t{:.{dp}f}\t{:.{dp}f}\t{}\t{}\t{}".format(
+ "{:,}\t{:,.{dp}f}\t{:,.{dp}f}\t{:,}\t{:,}\t{}".format(
j,
row.left,
row.right,
@@ -1548,11 +1548,11 @@ def _text_header_and_rows(self, limit=None):
decimal_places_times = 0 if self._columns_all_integer("time") else 8
for j in row_indexes:
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
row = self[j]
rows.append(
- "{}\t{:.{dp_c}f}\t{:.{dp_c}f}\t{}\t{}\t{}\t{:.{dp_t}f}\t{}".format(
+ "{:,}\t{:,.{dp_c}f}\t{:,.{dp_c}f}\t{:,}\t{:,}\t{:,}\t{:,.{dp_t}f}\t{}".format(
j,
row.left,
row.right,
@@ -1563,7 +1563,9 @@ def _text_header_and_rows(self, limit=None):
util.render_metadata(row.metadata),
dp_c=decimal_places_coords,
dp_t=decimal_places_times,
- ).split("\t")
+ ).split(
+ "\t"
+ )
)
return headers, rows
@@ -1760,11 +1762,11 @@ def _text_header_and_rows(self, limit=None):
decimal_places = 0 if self._columns_all_integer("position") else 8
for j in row_indexes:
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
row = self[j]
rows.append(
- "{}\t{:.{dp}f}\t{}\t{}".format(
+ "{:,}\t{:,.{dp}f}\t{}\t{}".format(
j,
row.position,
row.ancestral_state,
@@ -1977,11 +1979,11 @@ def _text_header_and_rows(self, limit=None):
decimal_places_times = 0 if self._columns_all_integer("time") else 8
for j in row_indexes:
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
row = self[j]
rows.append(
- "{}\t{}\t{}\t{:.{dp}f}\t{}\t{}\t{}".format(
+ "{:,}\t{:,}\t{:,}\t{:,.{dp}f}\t{}\t{:,}\t{}".format(
j,
row.site,
row.node,
@@ -2256,7 +2258,7 @@ def _text_header_and_rows(self, limit=None):
row_indexes = util.truncate_rows(self.num_rows, limit)
for j in row_indexes:
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
rows.append((str(j), util.render_metadata(self[j].metadata, length=70)))
return headers, rows
@@ -2507,7 +2509,7 @@ def _text_header_and_rows(self, limit=None):
row_indexes = util.truncate_rows(self.num_rows, limit)
for j in row_indexes:
if j == -1:
- rows.append(f"__skipped__{self.num_rows-limit}")
+ rows.append(f"__skipped__{self.num_rows - limit}")
else:
row = self[j]
rows.append(
diff --git a/python/tskit/trees.py b/python/tskit/trees.py
index fee125caa7..79ca182d67 100644
--- a/python/tskit/trees.py
+++ b/python/tskit/trees.py
@@ -2797,16 +2797,17 @@ def __str__(self):
Return a plain text summary of a tree in a tree sequence
"""
tree_rows = [
- ["Index", str(self.index)],
+ ["Index", f"{self.index:,}"],
[
"Interval",
- f"{self.interval.left:.8g}-{self.interval.right:.8g} ({self.span:.8g})",
+ f"{self.interval.left:,.8g}-{self.interval.right:,.8g}"
+ f"({self.span:,.8g})",
],
- ["Roots", str(self.num_roots)],
- ["Nodes", str(len(self.preorder()))],
- ["Sites", str(self.num_sites)],
- ["Mutations", str(self.num_mutations)],
- ["Total Branch Length", f"{self.total_branch_length:.8g}"],
+ ["Roots", f"{self.num_roots:,}"],
+ ["Nodes", f"{len(self.preorder()):,}"],
+ ["Sites", f"{self.num_sites:,}"],
+ ["Mutations", f"{self.num_mutations:,}"],
+ ["Total Branch Length", f"{self.total_branch_length:,.8g}"],
]
return util.unicode_table(tree_rows, title="Tree")
@@ -4369,17 +4370,14 @@ def __str__(self):
for name, table in self.tables.table_name_map.items():
table_rows.append(
[
- str(s)
- for s in [
- name.capitalize(),
- table.num_rows,
- util.naturalsize(table.nbytes),
- (
- "Yes"
- if hasattr(table, "metadata") and len(table.metadata) > 0
- else "No"
- ),
- ]
+ name.capitalize(),
+ f"{table.num_rows:,}",
+ util.naturalsize(table.nbytes),
+ (
+ "Yes"
+ if hasattr(table, "metadata") and len(table.metadata) > 0
+ else "No"
+ ),
]
)
return util.unicode_table(ts_rows, title="TreeSequence") + util.unicode_table(
diff --git a/python/tskit/util.py b/python/tskit/util.py
index 7f7a358fec..90dbe1720b 100644
--- a/python/tskit/util.py
+++ b/python/tskit/util.py
@@ -518,7 +518,7 @@ def tree_sequence_html(ts):
f"""
{name.capitalize()} |
- {table.num_rows} |
+ {table.num_rows:,} |
{naturalsize(table.nbytes)} |
{'✅' if hasattr(table, "metadata") and len(table.metadata) > 0
@@ -598,10 +598,10 @@ def tree_sequence_html(ts):
|
- Trees | {ts.num_trees} |
- Sequence Length | {ts.sequence_length} |
+ Trees | {ts.num_trees:,} |
+ Sequence Length | {ts.sequence_length:,} |
Time Units | {ts.time_units} |
- Sample Nodes | {ts.num_samples} |
+ Sample Nodes | {ts.num_samples:,} |
Total Size | {naturalsize(ts.nbytes)} |
Metadata | {md} |
@@ -670,13 +670,13 @@ def tree_html(tree):
- Index | {tree.index} |
- Interval | {tree.interval.left:.8g}-{tree.interval.right:.8g} ({tree.span:.8g}) |
- Roots | {tree.num_roots} |
- Nodes | {len(tree.preorder())} |
- Sites | {tree.num_sites} |
- Mutations | {tree.num_mutations} |
- Total Branch Length | {tree.total_branch_length:.8g} |
+ Index | {tree.index:,} |
+ Interval | {tree.interval.left:,.8g}-{tree.interval.right:,.8g} ({tree.span:,.8g}) |
+ Roots | {tree.num_roots:,} |
+ Nodes | {len(tree.preorder()):,} |
+ Sites | {tree.num_sites:,} |
+ Mutations | {tree.num_mutations:,} |
+ Total Branch Length | {tree.total_branch_length:,.8g} |
@@ -745,18 +745,18 @@ def variant_html(variant):
return (
html_body_head
+ f"""
- Site Id | {site_id} |
- Site Position | {site_position} |
- Number of Samples | {num_samples} |
- Number of Alleles | {num_alleles} |
+ Site Id | {site_id:,} |
+ Site Position | {site_position:,.8g} |
+ Number of Samples | {num_samples:,} |
+ Number of Alleles | {num_alleles:,} |
"""
+ "\n".join(
[
f"""Samples with Allele {'missing' if k is None
else "'" + k + "'"} | """
- + f"{counts[k]}"
+ + f"{counts[k]:,}"
+ " "
- + f"({freqs[k] * 100:.2g}%)"
+ + f"({freqs[k] * 100:,.2g}%)"
+ " |
"
for k in variant.alleles
]