88import copy
99from functools import partial
1010from itertools import product
11- from typing import Optional
11+ from typing import Any , Callable , DefaultDict , Dict , List , Optional , Sequence , Tuple
1212from uuid import uuid1
1313
1414import numpy as np
@@ -71,6 +71,11 @@ class Styler:
7171 The ``id`` takes the form ``T_<uuid>_row<num_row>_col<num_col>``
7272 where ``<uuid>`` is the unique identifier, ``<num_row>`` is the row
7373 number and ``<num_col>`` is the column number.
74+ na_rep : str, optional
75+ Representation for missing values.
76+ If ``na_rep`` is None, no special formatting is applied
77+
78+ .. versionadded:: 1.0.0
7479
7580 Attributes
7681 ----------
@@ -126,9 +131,10 @@ def __init__(
126131 caption = None ,
127132 table_attributes = None ,
128133 cell_ids = True ,
134+ na_rep : Optional [str ] = None ,
129135 ):
130- self .ctx = defaultdict (list )
131- self ._todo = []
136+ self .ctx : DefaultDict [ Tuple [ int , int ], List [ str ]] = defaultdict (list )
137+ self ._todo : List [ Tuple [ Callable , Tuple , Dict ]] = []
132138
133139 if not isinstance (data , (pd .Series , pd .DataFrame )):
134140 raise TypeError ("``data`` must be a Series or DataFrame" )
@@ -149,19 +155,24 @@ def __init__(
149155 self .precision = precision
150156 self .table_attributes = table_attributes
151157 self .hidden_index = False
152- self .hidden_columns = []
158+ self .hidden_columns : Sequence [ int ] = []
153159 self .cell_ids = cell_ids
160+ self .na_rep = na_rep
154161
155162 # display_funcs maps (row, col) -> formatting function
156163
157164 def default_display_func (x ):
158- if is_float (x ):
165+ if self .na_rep is not None and pd .isna (x ):
166+ return self .na_rep
167+ elif is_float (x ):
159168 display_format = "{0:.{precision}f}" .format (x , precision = self .precision )
160169 return display_format
161170 else :
162171 return x
163172
164- self ._display_funcs = defaultdict (lambda : default_display_func )
173+ self ._display_funcs : DefaultDict [
174+ Tuple [int , int ], Callable [[Any ], str ]
175+ ] = defaultdict (lambda : default_display_func )
165176
166177 def _repr_html_ (self ):
167178 """
@@ -416,16 +427,22 @@ def format_attr(pair):
416427 table_attributes = table_attr ,
417428 )
418429
419- def format (self , formatter , subset = None ):
430+ def format (self , formatter , subset = None , na_rep : Optional [ str ] = None ):
420431 """
421432 Format the text display value of cells.
422433
423434 Parameters
424435 ----------
425- formatter : str, callable, or dict
436+ formatter : str, callable, dict or None
437+ If ``formatter`` is None, the default formatter is used
426438 subset : IndexSlice
427439 An argument to ``DataFrame.loc`` that restricts which elements
428440 ``formatter`` is applied to.
441+ na_rep : str, optional
442+ Representation for missing values.
443+ If ``na_rep`` is None, no special formatting is applied
444+
445+ .. versionadded:: 1.0.0
429446
430447 Returns
431448 -------
@@ -451,6 +468,10 @@ def format(self, formatter, subset=None):
451468 >>> df['c'] = ['a', 'b', 'c', 'd']
452469 >>> df.style.format({'c': str.upper})
453470 """
471+ if formatter is None :
472+ assert self ._display_funcs .default_factory is not None
473+ formatter = self ._display_funcs .default_factory ()
474+
454475 if subset is None :
455476 row_locs = range (len (self .data ))
456477 col_locs = range (len (self .data .columns ))
@@ -466,16 +487,16 @@ def format(self, formatter, subset=None):
466487 if is_dict_like (formatter ):
467488 for col , col_formatter in formatter .items ():
468489 # formatter must be callable, so '{}' are converted to lambdas
469- col_formatter = _maybe_wrap_formatter (col_formatter )
490+ col_formatter = _maybe_wrap_formatter (col_formatter , na_rep )
470491 col_num = self .data .columns .get_indexer_for ([col ])[0 ]
471492
472493 for row_num in row_locs :
473494 self ._display_funcs [(row_num , col_num )] = col_formatter
474495 else :
475496 # single scalar to format all cells with
497+ formatter = _maybe_wrap_formatter (formatter , na_rep )
476498 locs = product (* (row_locs , col_locs ))
477499 for i , j in locs :
478- formatter = _maybe_wrap_formatter (formatter )
479500 self ._display_funcs [(i , j )] = formatter
480501 return self
481502
@@ -553,6 +574,7 @@ def _copy(self, deepcopy=False):
553574 caption = self .caption ,
554575 uuid = self .uuid ,
555576 table_styles = self .table_styles ,
577+ na_rep = self .na_rep ,
556578 )
557579 if deepcopy :
558580 styler .ctx = copy .deepcopy (self .ctx )
@@ -896,6 +918,23 @@ def set_table_styles(self, table_styles):
896918 self .table_styles = table_styles
897919 return self
898920
921+ def set_na_rep (self , na_rep : str ) -> "Styler" :
922+ """
923+ Set the missing data representation on a Styler.
924+
925+ .. versionadded:: 1.0.0
926+
927+ Parameters
928+ ----------
929+ na_rep : str
930+
931+ Returns
932+ -------
933+ self : Styler
934+ """
935+ self .na_rep = na_rep
936+ return self
937+
899938 def hide_index (self ):
900939 """
901940 Hide any indices from rendering.
@@ -1487,14 +1526,22 @@ def _get_level_lengths(index, hidden_elements=None):
14871526 return non_zero_lengths
14881527
14891528
1490- def _maybe_wrap_formatter (formatter ):
1529+ def _maybe_wrap_formatter (formatter , na_rep : Optional [ str ] ):
14911530 if isinstance (formatter , str ):
1492- return lambda x : formatter .format (x )
1531+ formatter_func = lambda x : formatter .format (x )
14931532 elif callable (formatter ):
1494- return formatter
1533+ formatter_func = formatter
14951534 else :
14961535 msg = (
14971536 "Expected a template string or callable, got {formatter} "
14981537 "instead" .format (formatter = formatter )
14991538 )
15001539 raise TypeError (msg )
1540+
1541+ if na_rep is None :
1542+ return formatter_func
1543+ elif isinstance (na_rep , str ):
1544+ return lambda x : na_rep if pd .isna (x ) else formatter_func (x )
1545+ else :
1546+ msg = "Expected a string, got {na_rep} instead" .format (na_rep = na_rep )
1547+ raise TypeError (msg )
0 commit comments