From c88469e4f0629498b6ec141ddbc54de1740184d0 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Sun, 19 Oct 2025 20:55:37 -0400 Subject: [PATCH 01/15] Add engineering notation formatting method --- great_tables/_formats.py | 344 +++++++++++++++++++++++++++++++++++++++ great_tables/gt.py | 2 + 2 files changed, 346 insertions(+) diff --git a/great_tables/_formats.py b/great_tables/_formats.py index 5d6e8e858..a06595d8c 100644 --- a/great_tables/_formats.py +++ b/great_tables/_formats.py @@ -863,6 +863,350 @@ def fmt_scientific_context( return x_formatted +def fmt_engineering( + self: GTSelf, + columns: SelectExpr = None, + rows: int | list[int] | None = None, + decimals: int = 2, + n_sigfig: int | None = None, + drop_trailing_zeros: bool = False, + drop_trailing_dec_mark: bool = True, + scale_by: float = 1, + exp_style: str = "x10n", + pattern: str = "{x}", + sep_mark: str = ",", + dec_mark: str = ".", + force_sign_m: bool = False, + force_sign_n: bool = False, + locale: str | None = None, +) -> GTSelf: + """ + Format values to engineering notation. + + With numeric values in a table, we can perform formatting so that the targeted values are + rendered in engineering notation, where numbers are written in the form of a mantissa (`m`) and + an exponent (`n`). When combined the construction is either of the form *m* x 10^*n* or *m*E*n*. + The mantissa is a number between `1` and `1000` and the exponent is a multiple of `3`. For + example, the number `0.0000345` can be written in engineering notation as `34.50 x 10^-6`. This + notation helps to simplify calculations and make it easier to compare numbers that are on very + different scales. + + Engineering notation is particularly useful as it aligns with SI prefixes (e.g., *milli-*, + *micro-*, *kilo-*, *mega-*). For instance, numbers in engineering notation with exponent `-3` + correspond to milli-units, while those with exponent `6` correspond to mega-units. + + We have fine control over the formatting task, with the following options: + + - decimals: choice of the number of decimal places, option to drop trailing zeros, and a choice + of the decimal symbol + - scaling: we can choose to scale targeted values by a multiplier value + - pattern: option to use a text pattern for decoration of the formatted values + - locale-based formatting: providing a locale ID will result in formatting specific to the + chosen locale + + Parameters + ---------- + columns + The columns to target. Can either be a single column name or a series of column names + provided in a list. + rows + In conjunction with `columns=`, we can specify which of their rows should undergo + formatting. The default is all rows, resulting in all rows in targeted columns being + formatted. Alternatively, we can supply a list of row indices. + decimals + The `decimals` values corresponds to the exact number of decimal places to use. A value such + as `2.34` can, for example, be formatted with `0` decimal places and it would result in + `"2"`. With `4` decimal places, the formatted value becomes `"2.3400"`. The trailing zeros + can be removed with `drop_trailing_zeros=True`. + n_sigfig + A option to format numbers to *n* significant figures. By default, this is `None` and thus + number values will be formatted according to the number of decimal places set via + `decimals`. If opting to format according to the rules of significant figures, `n_sigfig` + must be a number greater than or equal to `1`. Any values passed to the `decimals` and + `drop_trailing_zeros` arguments will be ignored. + drop_trailing_zeros + A boolean value that allows for removal of trailing zeros (those redundant zeros after the + decimal mark). + drop_trailing_dec_mark + A boolean value that determines whether decimal marks should always appear even if there are + no decimal digits to display after formatting (e.g., `23` becomes `23.` if `False`). By + default trailing decimal marks are not shown. + scale_by + All numeric values will be multiplied by the `scale_by` value before undergoing formatting. + Since the `default` value is `1`, no values will be changed unless a different multiplier + value is supplied. + exp_style + Style of formatting to use for the engineering notation formatting. By default this is + `"x10n"` but other options include using a single letter (e.g., `"e"`, `"E"`, etc.), a + letter followed by a `"1"` to signal a minimum digit width of one, or `"low-ten"` for using + a stylized `"10"` marker. + pattern + A formatting pattern that allows for decoration of the formatted value. The formatted value + is represented by the `{x}` (which can be used multiple times, if needed) and all other + characters will be interpreted as string literals. + sep_mark + The string to use as a separator between groups of digits. For example, using `sep_mark=","` + with a value of `1000` would result in a formatted value of `"1,000"`. This argument is + ignored if a `locale` is supplied (i.e., is not `None`). + dec_mark + The string to be used as the decimal mark. For example, using `dec_mark=","` with the value + `0.152` would result in a formatted value of `"0,152"`). This argument is ignored if a + `locale` is supplied (i.e., is not `None`). + force_sign_m + Should the plus sign be shown for positive values of the mantissa (first component)? This + would effectively show a sign for all values except zero on the first numeric component of + the notation. If so, use `True` (the default for this is `False`), where only negative + numbers will display a sign. + force_sign_n + Should the plus sign be shown for positive values of the exponent (second component)? This + would effectively show a sign for all values except zero on the second numeric component of + the notation. If so, use `True` (the default for this is `False`), where only negative + numbers will display a sign. + locale + An optional locale identifier that can be used for formatting values according the locale's + rules. Examples include `"en"` for English (United States) and `"fr"` for French (France). + + Returns + ------- + GT + The GT object is returned. This is the same object that the method is called on so that we + can facilitate method chaining. + + Adapting output to a specific `locale` + -------------------------------------- + This formatting method can adapt outputs according to a provided `locale` value. Examples + include `"en"` for English (United States) and `"fr"` for French (France). The use of a valid + locale ID here means separator and decimal marks will be correct for the given locale. Should + a value be provided in `dec_mark` or `sep_mark` it will be overridden by the locale's preferred + values. + + Note that a `locale` value provided here will override any global locale setting performed in + [`GT()`](`great_tables.GT`)'s own `locale` argument (it is settable there as a value received by + all other methods that have a `locale` argument). + + Examples + -------- + Let's define a DataFrame that contains two columns of values (one small and one large). After + creating a simple table with `GT()`, we'll call `fmt_engineering()` on both columns. + + ```{python} + import polars as pl + from great_tables import GT + + small_large_df = pl.DataFrame({ + "small": [10**-i for i in range(12, 0, -1)], + "large": [10**i for i in range(1, 13)] + }) + + GT(small_large_df).fmt_engineering() + ``` + + Notice that within the form of *m* x 10^*n*, the *n* values move in steps of 3 (away from 0), + and *m* values can have 1-3 digits before the decimal. Further to this, any values where *n* is + 0 results in a display of only *m* (the first two values in the `large` column demonstrates + this). + + Engineering notation expresses values so that they align to certain SI prefixes. Here is a table + that compares select SI prefixes and their symbols to decimal and engineering-notation + representations of the key numbers. + + ```{python} + import polars as pl + from great_tables import GT + + prefixes_df = pl.DataFrame({ + "name": [ + "peta", "tera", "giga", "mega", "kilo", + None, + "milli", "micro", "nano", "pico", "femto" + ], + "symbol": [ + "P", "T", "G", "M", "k", + None, + "m", "μ", "n", "p", "f" + ], + "decimal": [float(10**i) for i in range(15, -18, -3)], + }) + + prefixes_df = prefixes_df.with_columns( + engineering=pl.col("decimal") + ) + + ( + GT(prefixes_df) + .fmt_number(columns="decimal", n_sigfig=1) + .fmt_engineering(columns="engineering") + .sub_missing() + ) + ``` + + See Also + -------- + The functional version of this method, + [`val_fmt_engineering()`](`great_tables._formats_vals.val_fmt_engineering`), allows you to + format a single numerical value (or a list of them). + """ + + locale = _resolve_locale(self, locale=locale) + + # Use a locale-based marks if a locale ID is provided + sep_mark = _get_locale_sep_mark(default=sep_mark, use_seps=True, locale=locale) + dec_mark = _get_locale_dec_mark(default=dec_mark, locale=locale) + + pf_format = partial( + fmt_engineering_context, + data=self, + decimals=decimals, + n_sigfig=n_sigfig, + drop_trailing_zeros=drop_trailing_zeros, + drop_trailing_dec_mark=drop_trailing_dec_mark, + scale_by=scale_by, + exp_style=exp_style, + sep_mark=sep_mark, + dec_mark=dec_mark, + force_sign_m=force_sign_m, + force_sign_n=force_sign_n, + pattern=pattern, + ) + + return fmt_by_context(self, pf_format=pf_format, columns=columns, rows=rows) + + +# Generate a function that will operate on single `x` values in the table body +def fmt_engineering_context( + x: float | None, + data: GTData, + decimals: int, + n_sigfig: int | None, + drop_trailing_zeros: bool, + drop_trailing_dec_mark: bool, + scale_by: float, + exp_style: str, + sep_mark: str, + dec_mark: str, + force_sign_m: bool, + force_sign_n: bool, + pattern: str, + context: str, +) -> str: + if is_na(data._tbl_data, x): + return x + + # Scale `x` value by a defined `scale_by` value + x = x * scale_by + + # Determine whether the value is positive + is_positive = _has_positive_value(value=x) + + minus_mark = _context_minus_mark(context=context) + + # For engineering notation, we need to calculate the exponent that is a multiple of 3 + # and adjust the mantissa accordingly + if x == 0: + # Special case for zero + m_part = _value_to_decimal_notation( + value=0, + decimals=decimals, + n_sigfig=n_sigfig, + drop_trailing_zeros=drop_trailing_zeros, + drop_trailing_dec_mark=drop_trailing_dec_mark, + use_seps=False, + sep_mark=sep_mark, + dec_mark=dec_mark, + force_sign=False, + ) + n_part = "0" + power_3 = 0 + else: + # Calculate the power of 1000 (engineering notation uses multiples of 3) + import math + + power_3 = int(math.floor(math.log10(abs(x)) / 3) * 3) + + # Calculate the mantissa by dividing by 10^power_3 + mantissa = x / (10**power_3) + + # Format the mantissa + m_part = _value_to_decimal_notation( + value=mantissa, + decimals=decimals, + n_sigfig=n_sigfig, + drop_trailing_zeros=drop_trailing_zeros, + drop_trailing_dec_mark=drop_trailing_dec_mark, + use_seps=False, + sep_mark=sep_mark, + dec_mark=dec_mark, + force_sign=False, + ) + + n_part = str(power_3) + + # Force the positive sign to be present if the `force_sign_m` option is taken + if is_positive and force_sign_m: + m_part = "+" + m_part + + if exp_style == "x10n": + # Define the exponent string based on the `exp_style` that is the default + # ('x10n'); this is styled as 'x 10^n' instead of using a fixed symbol like 'E' + + # Determine which values don't require the (x 10^n) for engineering formatting + # since their exponent would be zero + small_pos = power_3 == 0 + + # Force the positive sign to be present if the `force_sign_n` option is taken + if force_sign_n and not _str_detect(n_part, "-"): + n_part = "+" + n_part + + # Implement minus sign replacement for `m_part` and `n_part` + m_part = _replace_minus(m_part, minus_mark=minus_mark) + n_part = _replace_minus(n_part, minus_mark=minus_mark) + + if small_pos: + # If the exponent is zero, then the formatted value is based on only the `m_part` + x_formatted = m_part + else: + # Get the set of exponent marks, which are used to decorate the `n_part` + exp_marks = _context_exp_marks(context=context) + + # Create the formatted string based on `exp_marks` and the two parts + x_formatted = m_part + exp_marks[0] + n_part + exp_marks[1] + + else: + # Define the exponent string based on the `exp_style` that's not the default + # value of 'x10n' + + exp_str = _context_exp_str(exp_style=exp_style) + + n_min_width = 1 if _str_detect(exp_style, r"^[a-zA-Z]1$") else 2 + + # The `n_part` will be extracted here and it must be padded to + # the defined minimum number of decimal places + if _str_detect(n_part, "-"): + n_part = _str_replace(n_part, "-", "") + n_part = n_part.rjust(n_min_width, "0") + n_part = "-" + n_part + else: + n_part = n_part.rjust(n_min_width, "0") + if force_sign_n: + n_part = "+" + n_part + + # Implement minus sign replacement for `m_part` and `n_part` + m_part = _replace_minus(m_part, minus_mark=minus_mark) + n_part = _replace_minus(n_part, minus_mark=minus_mark) + + x_formatted = m_part + exp_str + n_part + + # Use a supplied pattern specification to decorate the formatted value + if pattern != "{x}": + # Escape LaTeX special characters from literals in the pattern + if context == "latex": + pattern = escape_pattern_str_latex(pattern_str=pattern) + + x_formatted = pattern.replace("{x}", x_formatted) + + return x_formatted + + def fmt_percent( self: GTSelf, columns: SelectExpr = None, diff --git a/great_tables/gt.py b/great_tables/gt.py index db227822f..beff6ae71 100644 --- a/great_tables/gt.py +++ b/great_tables/gt.py @@ -15,6 +15,7 @@ fmt_currency, fmt_date, fmt_datetime, + fmt_engineering, fmt_flag, fmt_icon, fmt_image, @@ -227,6 +228,7 @@ def __init__( fmt_integer = fmt_integer fmt_percent = fmt_percent fmt_scientific = fmt_scientific + fmt_engineering = fmt_engineering fmt_currency = fmt_currency fmt_bytes = fmt_bytes fmt_roman = fmt_roman From fa02a9903ff93c0abed840ded8650e67c0b3ba3e Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Sun, 19 Oct 2025 20:56:05 -0400 Subject: [PATCH 02/15] Update _formats_vals.py --- great_tables/_formats_vals.py | 143 ++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 6 deletions(-) diff --git a/great_tables/_formats_vals.py b/great_tables/_formats_vals.py index 38d41a72f..3cb7284df 100644 --- a/great_tables/_formats_vals.py +++ b/great_tables/_formats_vals.py @@ -1,19 +1,17 @@ from __future__ import annotations +from functools import partial from pathlib import Path from typing import TYPE_CHECKING, Any from typing_extensions import TypeAlias -from ._gt_data import GTData, FramelessData -from ._tbl_data import SeriesLike, to_frame -from .gt import GT, _get_column_of_values - # TODO: these imports make it so that vals.fmt_integer does not require pandas # as part of broader work to remove the pandas dependency from val functions. from ._formats import _get_locale_sep_mark, _resolve_locale, fmt_integer_context -from functools import partial - +from ._gt_data import FramelessData, GTData +from ._tbl_data import SeriesLike, to_frame +from .gt import GT, _get_column_of_values if TYPE_CHECKING: from ._formats import DateStyle, TimeStyle @@ -421,6 +419,139 @@ def val_fmt_scientific( return vals_fmt +def val_fmt_engineering( + x: X, + decimals: int = 2, + n_sigfig: int | None = None, + drop_trailing_zeros: bool = False, + drop_trailing_dec_mark: bool = True, + scale_by: float = 1, + exp_style: str = "x10n", + pattern: str = "{x}", + sep_mark: str = ",", + dec_mark: str = ".", + force_sign_m: bool = False, + force_sign_n: bool = False, + locale: str | None = None, +) -> list[str]: + """ + Format values to engineering notation. + + With numeric values in a list, we can perform formatting so that the input values are rendered + in engineering notation, where numbers are written in the form of a mantissa (`m`) and an + exponent (`n`). When combined the construction is either of the form *m* x 10^*n* or *m*E*n*. + The mantissa is a number between `1` and `1000` and the exponent is a multiple of `3`. For + example, the number `0.0000345` can be written in engineering notation as `34.50 x 10^-6`. This + notation helps to simplify calculations and make it easier to compare numbers that are on very + different scales. + + Engineering notation is particularly useful as it aligns with SI prefixes (e.g., *milli-*, + *micro-*, *kilo-*, *mega-*). For instance, numbers in engineering notation with exponent `-3` + correspond to milli-units, while those with exponent `6` correspond to mega-units. + + We have fine control over the formatting task, with the following options: + + - decimals: choice of the number of decimal places, option to drop trailing zeros, and a choice + of the decimal symbol + - scaling: we can choose to scale targeted values by a multiplier value + - pattern: option to use a text pattern for decoration of the formatted values + - locale-based formatting: providing a locale ID will result in formatting specific to the + chosen locale + + Parameters + ---------- + x + A list of values to be formatted. + decimals + The `decimals` values corresponds to the exact number of decimal places to use. A value such + as `2.34` can, for example, be formatted with `0` decimal places and it would result in + `"2"`. With `4` decimal places, the formatted value becomes `"2.3400"`. The trailing zeros + can be removed with `drop_trailing_zeros=True`. + n_sigfig + A option to format numbers to *n* significant figures. By default, this is `None` and thus + number values will be formatted according to the number of decimal places set via + `decimals`. If opting to format according to the rules of significant figures, `n_sigfig` + must be a number greater than or equal to `1`. Any values passed to the `decimals` and + `drop_trailing_zeros` arguments will be ignored. + drop_trailing_zeros + A boolean value that allows for removal of trailing zeros (those redundant zeros after the + decimal mark). + drop_trailing_dec_mark + A boolean value that determines whether decimal marks should always appear even if there are + no decimal digits to display after formatting (e.g., `23` becomes `23.` if `False`). By + default trailing decimal marks are not shown. + scale_by + All numeric values will be multiplied by the `scale_by` value before undergoing formatting. + Since the `default` value is `1`, no values will be changed unless a different multiplier + value is supplied. + exp_style + Style of formatting to use for the engineering notation formatting. By default this is + `"x10n"` but other options include using a single letter (e.g., `"e"`, `"E"`, etc.), a + letter followed by a `"1"` to signal a minimum digit width of one, or `"low-ten"` for using + a stylized `"10"` marker. + pattern + A formatting pattern that allows for decoration of the formatted value. The formatted value + is represented by the `{x}` (which can be used multiple times, if needed) and all other + characters will be interpreted as string literals. + sep_mark + The string to use as a separator between groups of digits. For example, using `sep_mark=","` + with a value of `1000` would result in a formatted value of `"1,000"`. This argument is + ignored if a `locale` is supplied (i.e., is not `None`). + dec_mark + The string to be used as the decimal mark. For example, using `dec_mark=","` with the value + `0.152` would result in a formatted value of `"0,152"`). This argument is ignored if a + `locale` is supplied (i.e., is not `None`). + force_sign_m + Should the plus sign be shown for positive values of the mantissa (first component)? This + would effectively show a sign for all values except zero on the first numeric component of + the notation. If so, use `True` (the default for this is `False`), where only negative + numbers will display a sign. + force_sign_n + Should the plus sign be shown for positive values of the exponent (second component)? This + would effectively show a sign for all values except zero on the second numeric component of + the notation. If so, use `True` (the default for this is `False`), where only negative + numbers will display a sign. + locale + An optional locale identifier that can be used for formatting values according the locale's + rules. Examples include `"en"` for English (United States) and `"fr"` for French (France). + + Returns + ------- + list[str] + A list of formatted values is returned. + + Examples + -------- + ```{python} + from great_tables import vals + + vals.fmt_engineering([123456789, 0.000000425639], decimals=2) + ``` + """ + + gt_obj: GTData = _make_one_col_table(vals=x) + + gt_obj_fmt = gt_obj.fmt_engineering( + columns="x", + decimals=decimals, + n_sigfig=n_sigfig, + drop_trailing_zeros=drop_trailing_zeros, + drop_trailing_dec_mark=drop_trailing_dec_mark, + scale_by=scale_by, + exp_style=exp_style, + pattern=pattern, + sep_mark=sep_mark, + dec_mark=dec_mark, + force_sign_m=force_sign_m, + force_sign_n=force_sign_n, + locale=locale, + ) + + vals_fmt = _get_column_of_values(gt=gt_obj_fmt, column_name="x", context="html") + + return vals_fmt + + def val_fmt_percent( x: X, decimals: int = 2, From c3886c68b3d673cbceb8e3ca23d8e3d05e19e692 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Sun, 19 Oct 2025 20:56:18 -0400 Subject: [PATCH 03/15] Update vals.py --- great_tables/vals.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/great_tables/vals.py b/great_tables/vals.py index 81f79329b..8e1da3af2 100644 --- a/great_tables/vals.py +++ b/great_tables/vals.py @@ -3,15 +3,38 @@ from __future__ import annotations from ._formats_vals import ( - val_fmt_number as fmt_number, + val_fmt_bytes as fmt_bytes, +) +from ._formats_vals import ( + val_fmt_currency as fmt_currency, +) +from ._formats_vals import ( + val_fmt_date as fmt_date, +) +from ._formats_vals import ( + val_fmt_engineering as fmt_engineering, +) +from ._formats_vals import ( + val_fmt_image as fmt_image, +) +from ._formats_vals import ( val_fmt_integer as fmt_integer, - val_fmt_scientific as fmt_scientific, +) +from ._formats_vals import ( + val_fmt_markdown as fmt_markdown, +) +from ._formats_vals import ( + val_fmt_number as fmt_number, +) +from ._formats_vals import ( val_fmt_percent as fmt_percent, - val_fmt_currency as fmt_currency, +) +from ._formats_vals import ( val_fmt_roman as fmt_roman, - val_fmt_bytes as fmt_bytes, - val_fmt_date as fmt_date, +) +from ._formats_vals import ( + val_fmt_scientific as fmt_scientific, +) +from ._formats_vals import ( val_fmt_time as fmt_time, - val_fmt_markdown as fmt_markdown, - val_fmt_image as fmt_image, ) From e7f0d47dd34b8c10513f221d0de0225a07cbf4a0 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Sun, 19 Oct 2025 20:56:22 -0400 Subject: [PATCH 04/15] Update test_formats.py --- tests/test_formats.py | 185 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/tests/test_formats.py b/tests/test_formats.py index 3bfef4c32..a3cea2b50 100644 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -1154,6 +1154,191 @@ def test_fmt_scientific_case( assert x == x_out +# ------------------------------------------------------------------------------ +# Tests of `fmt_engineering()` +# ------------------------------------------------------------------------------ + + +@pytest.mark.parametrize( + "fmt_engineering_kwargs,x_in,x_out", + [ + ( + dict(decimals=2), + [ + 829300232923103939802.4, + 492032183020.5, + 84930284002.1, + 203820929.2, + 84729202.4, + 2323435.1, + 230323.4, + 50000.01, + 1000.001, + 10.00001, + 1.2345, + 0.12345, + 0.0000123456, + ], + [ + "829.30 × 1018", + "492.03 × 109", + "84.93 × 109", + "203.82 × 106", + "84.73 × 106", + "2.32 × 106", + "230.32 × 103", + "50.00 × 103", + "1.00 × 103", + "10.00", + "1.23", + "123.45 × 10−3", + "12.35 × 10−6", + ], + ), + ( + dict(decimals=2), + [ + -50000.01, + -1000.001, + -10.00001, + -12345, + -1234.5, + -123.45, + -1.2345, + -0.12345, + -0.0000123456, + ], + [ + "−50.00 × 103", + "−1.00 × 103", + "−10.00", + "−12.35 × 103", + "−1.23 × 103", + "−123.45", + "−1.23", + "−123.45 × 10−3", + "−12.35 × 10−6", + ], + ), + ( + dict(decimals=2, exp_style="E"), + [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [ + "−34.90E12", + "−3.45E03", + "−234.00E−06", + "0.00E00", + "75.34E−06", + "82.79E03", + "716.00E12", + ], + ), + ( + dict(decimals=2, exp_style="E1"), + [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [ + "−34.90E12", + "−3.45E3", + "−234.00E−6", + "0.00E0", + "75.34E−6", + "82.79E3", + "716.00E12", + ], + ), + ( + dict(decimals=2, force_sign_m=True), + [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [ + "−34.90 × 1012", + "−3.45 × 103", + "−234.00 × 10−6", + "0.00", + "+75.34 × 10−6", + "+82.79 × 103", + "+716.00 × 1012", + ], + ), + ( + dict(decimals=2, force_sign_n=True), + [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [ + "−34.90 × 10+12", + "−3.45 × 10+3", + "−234.00 × 10−6", + "0.00", + "75.34 × 10−6", + "82.79 × 10+3", + "716.00 × 10+12", + ], + ), + ( + dict(decimals=2, force_sign_m=True, force_sign_n=True), + [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [ + "−34.90 × 10+12", + "−3.45 × 10+3", + "−234.00 × 10−6", + "0.00", + "+75.34 × 10−6", + "+82.79 × 10+3", + "+716.00 × 10+12", + ], + ), + ( + dict(decimals=2, pattern="a {x} b"), + [1234.5, 0.000123], + [ + "a 1.23 × 103 b", + "a 123.00 × 10−6 b", + ], + ), + ( + dict(decimals=2, scale_by=1 / 1000), + [492032183020.5, 50000.01], + [ + "492.03 × 106", + "50.00", + ], + ), + ( + dict(decimals=5), + [ + -1.5e200, + -1.5e100, + -2.5, + -3.5e-100, + -3.5e-200, + 1.5e-200, + 1.5e-100, + 2.5, + 3.5e100, + 3.5e200, + ], + [ + "−150.00000 × 10198", + "−15.00000 × 1099", + "−2.50000", + "−350.00000 × 10−102", + "−35.00000 × 10−201", + "15.00000 × 10−201", + "150.00000 × 10−102", + "2.50000", + "35.00000 × 1099", + "350.00000 × 10198", + ], + ), + ], +) +def test_fmt_engineering_case( + fmt_engineering_kwargs: dict[str, Any], x_in: list[float], x_out: list[str] +): + df = pd.DataFrame({"x": x_in}) + gt = GT(df).fmt_engineering(columns="x", **fmt_engineering_kwargs) + x = _get_column_of_values(gt, column_name="x", context="html") + assert x == x_out + + # ------------------------------------------------------------------------------ # Tests of `fmt_currency()` # ------------------------------------------------------------------------------ From 26dbc84bb28748ad35299b645360ca2e4969765f Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Sun, 19 Oct 2025 20:56:43 -0400 Subject: [PATCH 05/15] Add GT.fmt_engineering to reference API --- docs/_quarto.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 4307f9a5b..e375c15fc 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -134,6 +134,7 @@ quartodoc: - GT.fmt_integer - GT.fmt_percent - GT.fmt_scientific + - GT.fmt_engineering - GT.fmt_currency - GT.fmt_bytes - GT.fmt_roman From 134089d9f1c57ea125de05aaa276767bdfb74543 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Sun, 19 Oct 2025 21:19:32 -0400 Subject: [PATCH 06/15] Add tests for some fmt_engineering() edge cases --- tests/test_formats.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_formats.py b/tests/test_formats.py index a3cea2b50..dbf5e7d71 100644 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -1336,9 +1336,46 @@ def test_fmt_engineering_case( df = pd.DataFrame({"x": x_in}) gt = GT(df).fmt_engineering(columns="x", **fmt_engineering_kwargs) x = _get_column_of_values(gt, column_name="x", context="html") + assert x == x_out +def test_fmt_engineering_with_missing_values(): + df = pd.DataFrame({"x": [1234.5, None, float("nan"), 0.000123]}) + gt = GT(df).fmt_engineering(columns="x", decimals=2) + x = _get_column_of_values(gt, column_name="x", context="html") + + assert x[1] == "" + assert x[2] == "" + + +def test_fmt_engineering_exp_style_force_sign(): + df = pd.DataFrame({"x": [1e6, 1e3, 1, 1e-3, 1e-6]}) + gt = GT(df).fmt_engineering(columns="x", decimals=2, exp_style="E1", force_sign_n=True) + x = _get_column_of_values(gt, column_name="x", context="html") + + assert x == [ + "1.00E+6", + "1.00E+3", + "1.00E+0", + "1.00E−3", + "1.00E−6", + ] + + +def test_fmt_engineering_latex_output(): + df = pd.DataFrame({"x": [1234.5, 0.000123]}) + gt = GT(df).fmt_engineering(columns="x", decimals=2, pattern="Value: {x} units") + x = _get_column_of_values(gt, column_name="x", context="latex") + + assert "Value:" in x[0] + assert "units" in x[0] + assert "1.23" in x[0] + assert "Value:" in x[1] + assert "units" in x[1] + assert "123.00" in x[1] + + # ------------------------------------------------------------------------------ # Tests of `fmt_currency()` # ------------------------------------------------------------------------------ From cec2fb0a39bcbf706a774a8dca741dbe7bcef6de Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Sun, 19 Oct 2025 21:24:21 -0400 Subject: [PATCH 07/15] Add tests for engineering notn formatter function --- tests/test_formats_vals.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_formats_vals.py b/tests/test_formats_vals.py index 660426a56..645347a62 100644 --- a/tests/test_formats_vals.py +++ b/tests/test_formats_vals.py @@ -29,3 +29,17 @@ def test_val_fmt_image_multiple(img_paths: Path): assert 'img src="data:image/svg+xml;base64' in img1 assert 'img src="data:image/svg+xml;base64' in img2 + + +def test_val_fmt_engineering_single(): + result = vals.fmt_engineering(1234.5, decimals=2) + assert result == ["1.23 × 103"] + + +def test_val_fmt_engineering_multiple(): + result = vals.fmt_engineering([1234.5, 0.000123, 1e6], decimals=2) + assert result == [ + "1.23 × 103", + "123.00 × 10−6", + "1.00 × 106", + ] From 550c1bdf54da1f86e6480db41c342f3fc4e76e3e Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Thu, 15 Jan 2026 10:27:54 -0500 Subject: [PATCH 08/15] Refactor vals.py imports and update Ruff config --- .vscode/settings.json | 4 +++- great_tables/vals.py | 38 ++++++++------------------------------ pyproject.toml | 4 ++++ 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6150244e4..2b73011a0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,7 @@ }, "python.testing.pytestArgs": ["tests"], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "ruff.format.args": ["--exclude", "great_tables/vals.py"], + "ruff.lint.args": ["--exclude", "great_tables/vals.py"] } diff --git a/great_tables/vals.py b/great_tables/vals.py index 8e1da3af2..11e9edbbb 100644 --- a/great_tables/vals.py +++ b/great_tables/vals.py @@ -2,39 +2,17 @@ from __future__ import annotations -from ._formats_vals import ( - val_fmt_bytes as fmt_bytes, -) -from ._formats_vals import ( - val_fmt_currency as fmt_currency, -) -from ._formats_vals import ( - val_fmt_date as fmt_date, -) -from ._formats_vals import ( - val_fmt_engineering as fmt_engineering, -) -from ._formats_vals import ( - val_fmt_image as fmt_image, -) -from ._formats_vals import ( - val_fmt_integer as fmt_integer, -) -from ._formats_vals import ( - val_fmt_markdown as fmt_markdown, -) from ._formats_vals import ( val_fmt_number as fmt_number, -) -from ._formats_vals import ( + val_fmt_integer as fmt_integer, + val_fmt_scientific as fmt_scientific, + val_fmt_engineering as fmt_engineering, val_fmt_percent as fmt_percent, -) -from ._formats_vals import ( + val_fmt_currency as fmt_currency, val_fmt_roman as fmt_roman, -) -from ._formats_vals import ( - val_fmt_scientific as fmt_scientific, -) -from ._formats_vals import ( + val_fmt_bytes as fmt_bytes, + val_fmt_date as fmt_date, val_fmt_time as fmt_time, + val_fmt_markdown as fmt_markdown, + val_fmt_image as fmt_image, ) diff --git a/pyproject.toml b/pyproject.toml index bb43b2c1f..1386a009e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,10 @@ markers = [ line-length = 100 +[tool.ruff.format] +exclude = ["great_tables/vals.py"] + + [tool.ruff.lint] exclude = ["docs", ".venv", "tests/*"] From 1f75793af31a5f27e42638f7e090d14917b3ca6a Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Thu, 15 Jan 2026 10:28:20 -0500 Subject: [PATCH 09/15] Remove redundant import in fmt_engineering_context() --- great_tables/_formats.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/great_tables/_formats.py b/great_tables/_formats.py index a06595d8c..a45e0ca7d 100644 --- a/great_tables/_formats.py +++ b/great_tables/_formats.py @@ -1119,8 +1119,6 @@ def fmt_engineering_context( power_3 = 0 else: # Calculate the power of 1000 (engineering notation uses multiples of 3) - import math - power_3 = int(math.floor(math.log10(abs(x)) / 3) * 3) # Calculate the mantissa by dividing by 10^power_3 From c100ec6dfe9ed0d0b2069a24f829c40a575b5f28 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Thu, 15 Jan 2026 10:28:34 -0500 Subject: [PATCH 10/15] Remove unused engineering notation function --- great_tables/_formats.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/great_tables/_formats.py b/great_tables/_formats.py index a45e0ca7d..7c49e4eb8 100644 --- a/great_tables/_formats.py +++ b/great_tables/_formats.py @@ -3271,29 +3271,6 @@ def _value_to_scientific_notation( return result -def _value_to_engineering_notation(value: int | float, n_sigfig: int, exp_style: str) -> str: - """ - Engineering notation. - - Returns a string value with the correct precision and an exponent that is divisible by three. - The `exp_style` text is placed between the decimal value and the exponent. - """ - - is_negative, sig_digits, dot_power, ten_power = _get_sci_parts(value, n_sigfig) - - eng_power = 3 * math.floor(ten_power / 3) - eng_dot = dot_power + ten_power - eng_power - - result = ( - ("-" if is_negative else "") - + _insert_decimal_mark(digits=sig_digits, power=eng_dot) - + exp_style - + str(eng_power) - ) - - return result - - def _format_number_n_sigfig( value: int | float, n_sigfig: int, From 7f5a65df0265b2167985f7b30b471a20bdf13da4 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Thu, 15 Jan 2026 10:28:53 -0500 Subject: [PATCH 11/15] Remove sep_mark parameter from engineering format functions --- great_tables/_formats.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/great_tables/_formats.py b/great_tables/_formats.py index 7c49e4eb8..c1cc44dac 100644 --- a/great_tables/_formats.py +++ b/great_tables/_formats.py @@ -1062,7 +1062,6 @@ def fmt_engineering( drop_trailing_dec_mark=drop_trailing_dec_mark, scale_by=scale_by, exp_style=exp_style, - sep_mark=sep_mark, dec_mark=dec_mark, force_sign_m=force_sign_m, force_sign_n=force_sign_n, @@ -1082,7 +1081,6 @@ def fmt_engineering_context( drop_trailing_dec_mark: bool, scale_by: float, exp_style: str, - sep_mark: str, dec_mark: str, force_sign_m: bool, force_sign_n: bool, @@ -1111,7 +1109,7 @@ def fmt_engineering_context( drop_trailing_zeros=drop_trailing_zeros, drop_trailing_dec_mark=drop_trailing_dec_mark, use_seps=False, - sep_mark=sep_mark, + sep_mark=",", dec_mark=dec_mark, force_sign=False, ) @@ -1132,7 +1130,7 @@ def fmt_engineering_context( drop_trailing_zeros=drop_trailing_zeros, drop_trailing_dec_mark=drop_trailing_dec_mark, use_seps=False, - sep_mark=sep_mark, + sep_mark=",", dec_mark=dec_mark, force_sign=False, ) From f9940451487ca2acd66c57c889566bac6d184bba Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Thu, 15 Jan 2026 10:29:19 -0500 Subject: [PATCH 12/15] Reorder and clean up imports in _formats_vals.py --- great_tables/_formats_vals.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/great_tables/_formats_vals.py b/great_tables/_formats_vals.py index d1e343487..064076f6a 100644 --- a/great_tables/_formats_vals.py +++ b/great_tables/_formats_vals.py @@ -4,15 +4,14 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, overload -from typing_extensions import TypeAlias, Concatenate, ParamSpec - -from ._gt_data import GTData, FramelessData -from ._tbl_data import PlExpr, SeriesLike, to_frame -from .gt import GT, _get_column_of_values +from typing_extensions import Concatenate, ParamSpec, TypeAlias # TODO: these imports make it so that vals.fmt_integer does not require pandas # as part of broader work to remove the pandas dependency from val functions. from ._formats import _get_locale_sep_mark, _resolve_locale, fmt_integer_context +from ._gt_data import FramelessData, GTData +from ._tbl_data import PlExpr, SeriesLike, to_frame +from .gt import GT, _get_column_of_values if TYPE_CHECKING: from ._formats import DateStyle, TimeStyle From 6771596a11cbc00efb137e714ce9a3e8f3796e86 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Thu, 15 Jan 2026 10:30:10 -0500 Subject: [PATCH 13/15] Simplify engineering format test cases --- tests/test_formats.py | 105 +++++++++--------------------------------- 1 file changed, 23 insertions(+), 82 deletions(-) diff --git a/tests/test_formats.py b/tests/test_formats.py index dbf5e7d71..19165e15b 100644 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -1162,129 +1162,86 @@ def test_fmt_scientific_case( @pytest.mark.parametrize( "fmt_engineering_kwargs,x_in,x_out", [ + # Basic decimals=2: test key ranges (very large, large, medium, small, very small) ( dict(decimals=2), [ - 829300232923103939802.4, - 492032183020.5, - 84930284002.1, - 203820929.2, - 84729202.4, - 2323435.1, - 230323.4, - 50000.01, - 1000.001, - 10.00001, - 1.2345, - 0.12345, - 0.0000123456, + 829300232923103939802.4, # Very large (10^18) + 2323435.1, # Medium large (10^6) + 1000.001, # Boundary (10^3) + 10.00001, # No exponent needed + 0.12345, # Small (10^-3) + 0.0000123456, # Very small (10^-6) ], [ "829.30 × 1018", - "492.03 × 109", - "84.93 × 109", - "203.82 × 106", - "84.73 × 106", "2.32 × 106", - "230.32 × 103", - "50.00 × 103", "1.00 × 103", "10.00", - "1.23", "123.45 × 10−3", "12.35 × 10−6", ], ), + # Negative values ( dict(decimals=2), - [ - -50000.01, - -1000.001, - -10.00001, - -12345, - -1234.5, - -123.45, - -1.2345, - -0.12345, - -0.0000123456, - ], + [-50000.01, -10.00001, -0.12345], [ "−50.00 × 103", - "−1.00 × 103", "−10.00", - "−12.35 × 103", - "−1.23 × 103", - "−123.45", - "−1.23", "−123.45 × 10−3", - "−12.35 × 10−6", ], ), + # exp_style="E" format ( dict(decimals=2, exp_style="E"), - [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [-3.49e13, 0, 82794], [ "−34.90E12", - "−3.45E03", - "−234.00E−06", "0.00E00", - "75.34E−06", "82.79E03", - "716.00E12", ], ), + # exp_style="E1" (single digit exponent) ( dict(decimals=2, exp_style="E1"), - [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [-3453, 0, 0.00007534], [ - "−34.90E12", "−3.45E3", - "−234.00E−6", "0.00E0", "75.34E−6", - "82.79E3", - "716.00E12", ], ), + # force_sign_m: positive/negative/zero ( dict(decimals=2, force_sign_m=True), - [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [-3453, 0, 82794], [ - "−34.90 × 1012", "−3.45 × 103", - "−234.00 × 10−6", "0.00", - "+75.34 × 10−6", "+82.79 × 103", - "+716.00 × 1012", ], ), + # force_sign_n: positive/negative exponents ( dict(decimals=2, force_sign_n=True), - [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [-0.000234, 82794], [ - "−34.90 × 10+12", - "−3.45 × 10+3", "−234.00 × 10−6", - "0.00", - "75.34 × 10−6", "82.79 × 10+3", - "716.00 × 10+12", ], ), + # force_sign_m and force_sign_n combined ( dict(decimals=2, force_sign_m=True, force_sign_n=True), - [-3.49e13, -3453, -0.000234, 0, 0.00007534, 82794, 7.16e14], + [-3453, 0, 82794], [ - "−34.90 × 10+12", "−3.45 × 10+3", - "−234.00 × 10−6", "0.00", - "+75.34 × 10−6", "+82.79 × 10+3", - "+716.00 × 10+12", ], ), + # pattern ( dict(decimals=2, pattern="a {x} b"), [1234.5, 0.000123], @@ -1293,6 +1250,7 @@ def test_fmt_scientific_case( "a 123.00 × 10−6 b", ], ), + # scale_by ( dict(decimals=2, scale_by=1 / 1000), [492032183020.5, 50000.01], @@ -1301,30 +1259,13 @@ def test_fmt_scientific_case( "50.00", ], ), + # Extreme values with higher precision ( dict(decimals=5), - [ - -1.5e200, - -1.5e100, - -2.5, - -3.5e-100, - -3.5e-200, - 1.5e-200, - 1.5e-100, - 2.5, - 3.5e100, - 3.5e200, - ], + [-1.5e200, 2.5, 3.5e200], [ "−150.00000 × 10198", - "−15.00000 × 1099", - "−2.50000", - "−350.00000 × 10−102", - "−35.00000 × 10−201", - "15.00000 × 10−201", - "150.00000 × 10−102", "2.50000", - "35.00000 × 1099", "350.00000 × 10198", ], ), From 69e8f85603274c56bcc88df5c1b7a31dd7877eec Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Thu, 15 Jan 2026 10:46:18 -0500 Subject: [PATCH 14/15] Remove sep_mark arg from eng formatters --- great_tables/_formats.py | 13 +++---------- great_tables/_formats_vals.py | 6 ------ 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/great_tables/_formats.py b/great_tables/_formats.py index c1cc44dac..d768de0e2 100644 --- a/great_tables/_formats.py +++ b/great_tables/_formats.py @@ -874,7 +874,6 @@ def fmt_engineering( scale_by: float = 1, exp_style: str = "x10n", pattern: str = "{x}", - sep_mark: str = ",", dec_mark: str = ".", force_sign_m: bool = False, force_sign_n: bool = False, @@ -944,10 +943,6 @@ def fmt_engineering( A formatting pattern that allows for decoration of the formatted value. The formatted value is represented by the `{x}` (which can be used multiple times, if needed) and all other characters will be interpreted as string literals. - sep_mark - The string to use as a separator between groups of digits. For example, using `sep_mark=","` - with a value of `1000` would result in a formatted value of `"1,000"`. This argument is - ignored if a `locale` is supplied (i.e., is not `None`). dec_mark The string to be used as the decimal mark. For example, using `dec_mark=","` with the value `0.152` would result in a formatted value of `"0,152"`). This argument is ignored if a @@ -976,9 +971,8 @@ def fmt_engineering( -------------------------------------- This formatting method can adapt outputs according to a provided `locale` value. Examples include `"en"` for English (United States) and `"fr"` for French (France). The use of a valid - locale ID here means separator and decimal marks will be correct for the given locale. Should - a value be provided in `dec_mark` or `sep_mark` it will be overridden by the locale's preferred - values. + locale ID here means decimal marks will be correct for the given locale. Should a value be + provided in `dec_mark` it will be overridden by the locale's preferred values. Note that a `locale` value provided here will override any global locale setting performed in [`GT()`](`great_tables.GT`)'s own `locale` argument (it is settable there as a value received by @@ -1049,8 +1043,7 @@ def fmt_engineering( locale = _resolve_locale(self, locale=locale) - # Use a locale-based marks if a locale ID is provided - sep_mark = _get_locale_sep_mark(default=sep_mark, use_seps=True, locale=locale) + # Use a locale-based decimal mark if a locale ID is provided dec_mark = _get_locale_dec_mark(default=dec_mark, locale=locale) pf_format = partial( diff --git a/great_tables/_formats_vals.py b/great_tables/_formats_vals.py index 064076f6a..8abb1e371 100644 --- a/great_tables/_formats_vals.py +++ b/great_tables/_formats_vals.py @@ -464,7 +464,6 @@ def val_fmt_engineering( scale_by: float = 1, exp_style: str = "x10n", pattern: str = "{x}", - sep_mark: str = ",", dec_mark: str = ".", force_sign_m: bool = False, force_sign_n: bool = False, @@ -529,10 +528,6 @@ def val_fmt_engineering( A formatting pattern that allows for decoration of the formatted value. The formatted value is represented by the `{x}` (which can be used multiple times, if needed) and all other characters will be interpreted as string literals. - sep_mark - The string to use as a separator between groups of digits. For example, using `sep_mark=","` - with a value of `1000` would result in a formatted value of `"1,000"`. This argument is - ignored if a `locale` is supplied (i.e., is not `None`). dec_mark The string to be used as the decimal mark. For example, using `dec_mark=","` with the value `0.152` would result in a formatted value of `"0,152"`). This argument is ignored if a @@ -576,7 +571,6 @@ def val_fmt_engineering( scale_by=scale_by, exp_style=exp_style, pattern=pattern, - sep_mark=sep_mark, dec_mark=dec_mark, force_sign_m=force_sign_m, force_sign_n=force_sign_n, From 0692903e158331d26e3b0adb6d0e8fd61c7eea65 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Fri, 16 Jan 2026 09:31:18 -0500 Subject: [PATCH 15/15] Include simple example to start off section --- great_tables/_formats.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/great_tables/_formats.py b/great_tables/_formats.py index d768de0e2..f05f23205 100644 --- a/great_tables/_formats.py +++ b/great_tables/_formats.py @@ -980,13 +980,27 @@ def fmt_engineering( Examples -------- - Let's define a DataFrame that contains two columns of values (one small and one large). After - creating a simple table with `GT()`, we'll call `fmt_engineering()` on both columns. + With numeric values in a table, we can perform formatting so that the targeted values are + rendered in engineering notation. For example, the number `0.0000345` can be written in + engineering notation as `34.50 x 10^-6`. ```{python} import polars as pl from great_tables import GT + numbers_df = pl.DataFrame({ + "numbers": [0.0000345, 3450, 3450000] + }) + + GT(numbers_df).fmt_engineering() + ``` + + Notice that in each case, the exponent is a multiple of `3`. + + Let's define a DataFrame that contains two columns of values (one small and one large). After + creating a simple table with `GT()`, we'll call `fmt_engineering()` on both columns. + + ```{python} small_large_df = pl.DataFrame({ "small": [10**-i for i in range(12, 0, -1)], "large": [10**i for i in range(1, 13)]