38
38
import pandas .core .common as com
39
39
40
40
jinja2 = import_optional_dependency ("jinja2" , extra = "DataFrame.style requires jinja2." )
41
- from markupsafe import escape as escape_func # markupsafe is jinja2 dependency
41
+ from markupsafe import escape as escape_html # markupsafe is jinja2 dependency
42
42
43
43
BaseFormatter = Union [str , Callable ]
44
44
ExtFormatter = Union [BaseFormatter , Dict [Any , Optional [BaseFormatter ]]]
@@ -366,6 +366,8 @@ def format(
366
366
subset : slice | Sequence [Any ] | None = None ,
367
367
na_rep : str | None = None ,
368
368
precision : int | None = None ,
369
+ decimal : str = "." ,
370
+ thousands : str | None = None ,
369
371
escape : bool = False ,
370
372
) -> StylerRenderer :
371
373
"""
@@ -390,6 +392,16 @@ def format(
390
392
391
393
.. versionadded:: 1.3.0
392
394
395
+ decimal : str, default "."
396
+ Character used as decimal separator for floats, complex and integers
397
+
398
+ .. versionadded:: 1.3.0
399
+
400
+ thousands : str, optional, default None
401
+ Character used as thousands separator for floats, complex and integers
402
+
403
+ .. versionadded:: 1.3.0
404
+
393
405
escape : bool, default False
394
406
Replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in cell display
395
407
string with HTML-safe sequences. Escaping is done before ``formatter``.
@@ -482,6 +494,8 @@ def format(
482
494
formatter is None ,
483
495
subset is None ,
484
496
precision is None ,
497
+ decimal == "." ,
498
+ thousands is None ,
485
499
na_rep is None ,
486
500
escape is False ,
487
501
)
@@ -502,8 +516,14 @@ def format(
502
516
format_func = formatter [col ]
503
517
except KeyError :
504
518
format_func = None
519
+
505
520
format_func = _maybe_wrap_formatter (
506
- format_func , na_rep = na_rep , precision = precision , escape = escape
521
+ format_func ,
522
+ na_rep = na_rep ,
523
+ precision = precision ,
524
+ decimal = decimal ,
525
+ thousands = thousands ,
526
+ escape = escape ,
507
527
)
508
528
509
529
for row , value in data [[col ]].itertuples ():
@@ -607,7 +627,7 @@ def _format_table_styles(styles: CSSStyles) -> CSSStyles:
607
627
]
608
628
609
629
610
- def _default_formatter (x : Any , precision : int ) -> Any :
630
+ def _default_formatter (x : Any , precision : int , thousands : bool = False ) -> Any :
611
631
"""
612
632
Format the display of a value
613
633
@@ -617,51 +637,100 @@ def _default_formatter(x: Any, precision: int) -> Any:
617
637
Input variable to be formatted
618
638
precision : Int
619
639
Floating point precision used if ``x`` is float or complex.
640
+ thousands : bool, default False
641
+ Whether to group digits with thousands separated with ",".
620
642
621
643
Returns
622
644
-------
623
645
value : Any
624
- Matches input type, or string if input is float or complex.
646
+ Matches input type, or string if input is float or complex or int with sep .
625
647
"""
626
648
if isinstance (x , (float , complex )):
649
+ if thousands :
650
+ return f"{ x :,.{precision }f} "
627
651
return f"{ x :.{precision }f} "
652
+ elif isinstance (x , int ) and thousands :
653
+ return f"{ x :,.0f} "
654
+ return x
655
+
656
+
657
+ def _wrap_decimal_thousands (
658
+ formatter : Callable , decimal : str , thousands : str | None
659
+ ) -> Callable :
660
+ """
661
+ Takes a string formatting function and wraps logic to deal with thousands and
662
+ decimal parameters, in the case that they are non-standard and that the input
663
+ is a (float, complex, int).
664
+ """
665
+
666
+ def wrapper (x ):
667
+ if isinstance (x , (float , complex , int )):
668
+ if decimal != "." and thousands is not None and thousands != "," :
669
+ return (
670
+ formatter (x )
671
+ .replace ("," , "§_§-" ) # rare string to avoid "," <-> "." clash.
672
+ .replace ("." , decimal )
673
+ .replace ("§_§-" , thousands )
674
+ )
675
+ elif decimal != "." and (thousands is None or thousands == "," ):
676
+ return formatter (x ).replace ("." , decimal )
677
+ elif decimal == "." and thousands is not None and thousands != "," :
678
+ return formatter (x ).replace ("," , thousands )
679
+ return formatter (x )
680
+
681
+ return wrapper
682
+
683
+
684
+ def _str_escape_html (x ):
685
+ """if escaping html: only use on str, else return input"""
686
+ if isinstance (x , str ):
687
+ return escape_html (x )
628
688
return x
629
689
630
690
631
691
def _maybe_wrap_formatter (
632
692
formatter : BaseFormatter | None = None ,
633
693
na_rep : str | None = None ,
634
694
precision : int | None = None ,
695
+ decimal : str = "." ,
696
+ thousands : str | None = None ,
635
697
escape : bool = False ,
636
698
) -> Callable :
637
699
"""
638
700
Allows formatters to be expressed as str, callable or None, where None returns
639
701
a default formatting function. wraps with na_rep, and precision where they are
640
702
available.
641
703
"""
704
+ # Get initial func from input string, input callable, or from default factory
642
705
if isinstance (formatter , str ):
643
- formatter_func = lambda x : formatter .format (x )
706
+ func_0 = lambda x : formatter .format (x )
644
707
elif callable (formatter ):
645
- formatter_func = formatter
708
+ func_0 = formatter
646
709
elif formatter is None :
647
710
precision = get_option ("display.precision" ) if precision is None else precision
648
- formatter_func = partial (_default_formatter , precision = precision )
711
+ func_0 = partial (
712
+ _default_formatter , precision = precision , thousands = (thousands is not None )
713
+ )
649
714
else :
650
715
raise TypeError (f"'formatter' expected str or callable, got { type (formatter )} " )
651
716
652
- def _str_escape (x , escape : bool ):
653
- """if escaping: only use on str, else return input"""
654
- if escape and isinstance (x , str ):
655
- return escape_func (x )
656
- else :
657
- return x
717
+ # Replace HTML chars if escaping
718
+ if escape :
719
+ func_1 = lambda x : func_0 (_str_escape_html (x ))
720
+ else :
721
+ func_1 = func_0
658
722
659
- display_func = lambda x : formatter_func (partial (_str_escape , escape = escape )(x ))
723
+ # Replace decimals and thousands if non-standard inputs detected
724
+ if decimal != "." or (thousands is not None and thousands != "," ):
725
+ func_2 = _wrap_decimal_thousands (func_1 , decimal = decimal , thousands = thousands )
726
+ else :
727
+ func_2 = func_1
660
728
729
+ # Replace missing values if na_rep
661
730
if na_rep is None :
662
- return display_func
731
+ return func_2
663
732
else :
664
- return lambda x : na_rep if isna (x ) else display_func (x )
733
+ return lambda x : na_rep if isna (x ) else func_2 (x )
665
734
666
735
667
736
def non_reducing_slice (slice_ ):
0 commit comments