Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: accounting notation for fmt_number(), fmt_percent(), fmt_integer() and fmt_currency() #513

Merged
merged 10 commits into from
Dec 9, 2024
83 changes: 68 additions & 15 deletions great_tables/_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@
DataFrameLike,
PlExpr,
SelectExpr,
_get_column_dtype,
is_na,
is_series,
to_list,
_get_column_dtype,
)
from ._text import _md_html, escape_pattern_str_latex
from ._utils import _str_detect, _str_replace
from ._utils_nanoplots import _generate_nanoplot


if TYPE_CHECKING:
from ._types import GTSelf

Expand Down Expand Up @@ -153,6 +152,7 @@
drop_trailing_zeros: bool = False,
drop_trailing_dec_mark: bool = True,
use_seps: bool = True,
accounting: bool = False,
scale_by: float = 1,
compact: bool = False,
pattern: str = "{x}",
Expand Down Expand Up @@ -211,6 +211,9 @@
The `use_seps` option allows for the use of digit group separators. The type of digit group
separator is set by `sep_mark` and overridden if a locale ID is provided to `locale`. This
setting is `True` by default.
accounting
Whether to use accounting style, which wraps negative numbers in parentheses instead of
using a minus sign.
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
Expand Down Expand Up @@ -294,6 +297,7 @@
drop_trailing_zeros=drop_trailing_zeros,
drop_trailing_dec_mark=drop_trailing_dec_mark,
use_seps=use_seps,
accounting=accounting,
scale_by=scale_by,
compact=compact,
sep_mark=sep_mark,
Expand All @@ -313,6 +317,7 @@
drop_trailing_zeros: bool,
drop_trailing_dec_mark: bool,
use_seps: bool,
accounting: bool,
scale_by: float,
compact: bool,
sep_mark: str,
Expand Down Expand Up @@ -355,10 +360,14 @@
force_sign=force_sign,
)

# Implement minus sign replacement for `x_formatted`
# Implement minus sign replacement for `x_formatted` or use accounting style
if is_negative:
minus_mark = _context_minus_mark(context=context)
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)
if accounting:
x_formatted = f"({_remove_minus(x_formatted)})"

else:
minus_mark = _context_minus_mark(context=context)
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)

# Use a supplied pattern specification to decorate the formatted value
if pattern != "{x}":
Expand All @@ -377,6 +386,7 @@
rows: int | list[int] | None = None,
use_seps: bool = True,
scale_by: float = 1,
accounting: bool = False,
compact: bool = False,
pattern: str = "{x}",
sep_mark: str = ",",
Expand Down Expand Up @@ -417,6 +427,9 @@
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.
accounting
Whether to use accounting style, which wraps negative numbers in parentheses instead of
using a minus sign.
compact
A boolean value that allows for compact formatting of numeric values. Values will be scaled
and decorated with the appropriate suffixes (e.g., `1230` becomes `1K`, and `1230000`
Expand Down Expand Up @@ -487,6 +500,7 @@
data=self,
use_seps=use_seps,
scale_by=scale_by,
accounting=accounting,
compact=compact,
sep_mark=sep_mark,
force_sign=force_sign,
Expand All @@ -501,6 +515,7 @@
data: GTData,
use_seps: bool,
scale_by: float,
accounting: bool,
compact: bool,
sep_mark: str,
force_sign: bool,
Expand Down Expand Up @@ -542,10 +557,14 @@
force_sign=force_sign,
)

# Implement minus sign replacement for `x_formatted`
# Implement minus sign replacement for `x_formatted` or use accounting style
if is_negative:
minus_mark = _context_minus_mark(context=context)
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)
if accounting:
x_formatted = f"({_remove_minus(x_formatted)})"

else:
minus_mark = _context_minus_mark(context=context)
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)

# Use a supplied pattern specification to decorate the formatted value
if pattern != "{x}":
Expand Down Expand Up @@ -848,6 +867,7 @@
drop_trailing_dec_mark: bool = True,
scale_values: bool = True,
use_seps: bool = True,
accounting: bool = False,
pattern: str = "{x}",
sep_mark: str = ",",
dec_mark: str = ".",
Expand Down Expand Up @@ -907,6 +927,9 @@
The `use_seps` option allows for the use of digit group separators. The type of digit group
separator is set by `sep_mark` and overridden if a locale ID is provided to `locale`. This
setting is `True` by default.
accounting
Whether to use accounting style, which wraps negative numbers in parentheses instead of
using a minus sign.
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
Expand Down Expand Up @@ -994,6 +1017,7 @@
drop_trailing_zeros=drop_trailing_zeros,
drop_trailing_dec_mark=drop_trailing_dec_mark,
use_seps=use_seps,
accounting=accounting,
scale_by=scale_by,
sep_mark=sep_mark,
dec_mark=dec_mark,
Expand All @@ -1013,6 +1037,7 @@
drop_trailing_zeros: bool,
drop_trailing_dec_mark: bool,
use_seps: bool,
accounting: bool,
scale_by: float,
sep_mark: str,
dec_mark: str,
Expand Down Expand Up @@ -1066,10 +1091,14 @@
else:
x_formatted = percent_pattern.replace("{x}", x_formatted)

# Implement minus sign replacement for `x_formatted`
# Implement minus sign replacement for `x_formatted` or use accounting style
if is_negative:
minus_mark = _context_minus_mark(context="html")
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)
if accounting:
x_formatted = f"({_remove_minus(x_formatted)})"

else:
minus_mark = _context_minus_mark(context=context)
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)

# Use a supplied pattern specification to decorate the formatted value
if pattern != "{x}":
Expand All @@ -1091,6 +1120,7 @@
decimals: int | None = None,
drop_trailing_dec_mark: bool = True,
use_seps: bool = True,
accounting: bool = False,
scale_by: float = 1,
pattern: str = "{x}",
sep_mark: str = ",",
Expand Down Expand Up @@ -1150,6 +1180,9 @@
The `use_seps` option allows for the use of digit group separators. The type of digit group
separator is set by `sep_mark` and overridden if a locale ID is provided to `locale`. This
setting is `True` by default.
accounting
Whether to use accounting style, which wraps negative numbers in parentheses instead of
using a minus sign.
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
Expand Down Expand Up @@ -1253,6 +1286,7 @@
decimals=decimals,
drop_trailing_dec_mark=drop_trailing_dec_mark,
use_seps=use_seps,
accounting=accounting,
scale_by=scale_by,
sep_mark=sep_mark,
dec_mark=dec_mark,
Expand All @@ -1272,6 +1306,7 @@
decimals: int,
drop_trailing_dec_mark: bool,
use_seps: bool,
accounting: bool,
scale_by: float,
sep_mark: str,
dec_mark: str,
Expand Down Expand Up @@ -1330,10 +1365,14 @@
else:
x_formatted = currency_pattern.replace("{x}", x_formatted)

# Implement minus sign replacement for `x_formatted`
# Implement minus sign replacement for `x_formatted` or use accounting style
if is_negative:
minus_mark = _context_minus_mark(context=context)
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)
if accounting:
x_formatted = f"({_remove_minus(x_formatted)})"

else:
minus_mark = _context_minus_mark(context=context)
x_formatted = _replace_minus(x_formatted, minus_mark=minus_mark)

# Use a supplied pattern specification to decorate the formatted value
if pattern != "{x}":
Expand Down Expand Up @@ -2942,6 +2981,19 @@
return _str_replace(string, "-", minus_mark)


def _remove_minus(string: str) -> str:
"""
Removes all occurrences of the minus sign '-' in the given string.

Args:
string (str): The input string.

Returns:
str: The modified string with the minus sign removed.
"""
return _str_replace(string, "-", "")


T_dict = TypeVar("T_dict", bound=TypedDict)


Expand Down Expand Up @@ -3764,9 +3816,10 @@
return span

def to_latex(self, val: Any):
from ._gt_data import FormatterSkipElement
from warnings import warn

from ._gt_data import FormatterSkipElement

Check warning on line 3821 in great_tables/_formats.py

View check run for this annotation

Codecov / codecov/patch

great_tables/_formats.py#L3821

Added line #L3821 was not covered by tests

warn("fmt_image() is not currently implemented in LaTeX output.")

return FormatterSkipElement()
Expand Down
20 changes: 20 additions & 0 deletions great_tables/_formats_vals.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def val_fmt_number(
drop_trailing_zeros: bool = False,
drop_trailing_dec_mark: bool = True,
use_seps: bool = True,
accounting: bool = False,
scale_by: float = 1,
compact: bool = False,
pattern: str = "{x}",
Expand Down Expand Up @@ -108,6 +109,9 @@ def val_fmt_number(
The `use_seps` option allows for the use of digit group separators. The type of digit group
separator is set by `sep_mark` and overridden if a locale ID is provided to `locale`. This
setting is `True` by default.
accounting
An option to use accounting style for values. Normally, negative values will be shown with a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT of changing these docstrings to something like "Whether to use accounting style, which wraps negative numbers in parentheses instead of using a minus sign."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, now done!

minus sign but using accounting style will instead put any negative values in parentheses.
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
Expand Down Expand Up @@ -151,6 +155,7 @@ def val_fmt_number(
drop_trailing_zeros=drop_trailing_zeros,
drop_trailing_dec_mark=drop_trailing_dec_mark,
use_seps=use_seps,
accounting=accounting,
scale_by=scale_by,
compact=compact,
pattern=pattern,
Expand All @@ -168,6 +173,7 @@ def val_fmt_number(
def val_fmt_integer(
x: X,
use_seps: bool = True,
accounting: bool = False,
scale_by: float = 1,
compact: bool = False,
pattern: str = "{x}",
Expand Down Expand Up @@ -200,6 +206,9 @@ def val_fmt_integer(
The `use_seps` option allows for the use of digit group separators. The type of digit group
separator is set by `sep_mark` and overridden if a locale ID is provided to `locale`. This
setting is `True` by default.
accounting
An option to use accounting style for values. Normally, negative values will be shown with a
minus sign but using accounting style will instead put any negative values in parentheses.
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
Expand Down Expand Up @@ -235,6 +244,7 @@ def val_fmt_integer(
gt_obj_fmt = gt_obj.fmt_integer(
columns="x",
use_seps=use_seps,
accounting=accounting,
scale_by=scale_by,
compact=compact,
pattern=pattern,
Expand Down Expand Up @@ -376,6 +386,7 @@ def val_fmt_percent(
drop_trailing_zeros: bool = False,
drop_trailing_dec_mark: bool = True,
scale_values: bool = True,
accounting: bool = False,
use_seps: bool = True,
pattern: str = "{x}",
sep_mark: str = ",",
Expand Down Expand Up @@ -427,6 +438,9 @@ def val_fmt_percent(
performed since the expectation is that incoming values are usually proportional. Setting to
`False` signifies that the values are already scaled and require only the percent sign when
formatted.
accounting
An option to use accounting style for values. Normally, negative values will be shown with a
minus sign but using accounting style will instead put any negative values in parentheses.
use_seps
The `use_seps` option allows for the use of digit group separators. The type of digit group
separator is set by `sep_mark` and overridden if a locale ID is provided to `locale`. This
Expand Down Expand Up @@ -471,6 +485,7 @@ def val_fmt_percent(
drop_trailing_zeros=drop_trailing_zeros,
drop_trailing_dec_mark=drop_trailing_dec_mark,
scale_values=scale_values,
accounting=accounting,
use_seps=use_seps,
pattern=pattern,
sep_mark=sep_mark,
Expand All @@ -492,6 +507,7 @@ def val_fmt_currency(
use_subunits: bool = True,
decimals: int | None = None,
drop_trailing_dec_mark: bool = True,
accounting: bool = False,
use_seps: bool = True,
scale_by: float = 1,
pattern: str = "{x}",
Expand Down Expand Up @@ -543,6 +559,9 @@ def val_fmt_currency(
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.
accounting
An option to use accounting style for values. Normally, negative values will be shown with a
minus sign but using accounting style will instead put any negative values in parentheses.
use_seps
The `use_seps` option allows for the use of digit group separators. The type of digit group
separator is set by `sep_mark` and overridden if a locale ID is provided to `locale`. This
Expand Down Expand Up @@ -591,6 +610,7 @@ def val_fmt_currency(
use_subunits=use_subunits,
decimals=decimals,
drop_trailing_dec_mark=drop_trailing_dec_mark,
accounting=accounting,
use_seps=use_seps,
scale_by=scale_by,
pattern=pattern,
Expand Down
Loading
Loading